<!DOCTYPE html>
<html lang="zh-CN" dir="ltr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>10.8 水印实例: 文本绘制与离屏渲染 | 落光的Pro博客</title>
    <meta name="description" content="Vite & Vue powered static site generator.">
    <link rel="preload stylesheet" href="./assets/style.4b0df91d.css" as="style">
    
    <script type="module" src="./assets/app.0acbd3fd.js"></script>
    <link rel="preload" href="./assets/inter-roman-latin.2ed14f66.woff2" as="font" type="font/woff2" crossorigin="">
    <link rel="modulepreload" href="./assets/chunks/framework.eaf25f5b.js">
    <link rel="modulepreload" href="./assets/chunks/theme.ce44a0e6.js">
    <link rel="modulepreload" href="./assets/largeFrontEnd_flutter_chapter10_watermark.md.2e4ddf03.lean.js">
    <script id="check-dark-light">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
  </head>
  <body>
    <div id="app"><div class="Layout" data-v-1919c326><!--[--><!--]--><!--[--><span tabindex="-1" data-v-0f60ec36></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-0f60ec36> Skip to content </a><!--]--><!----><header class="VPNav" data-v-1919c326 data-v-7e5bc4a5><div class="VPNavBar has-sidebar" data-v-7e5bc4a5 data-v-a0fd61f4><div class="container" data-v-a0fd61f4><div class="title" data-v-a0fd61f4><div class="VPNavBarTitle has-sidebar" data-v-a0fd61f4 data-v-86d1bed8><a class="title" href="./" data-v-86d1bed8><!--[--><!--]--><!--[--><img class="VPImage logo" src="./icon-radius.png" alt data-v-8426fc1a><!--]--><!--[-->落光的Pro博客<!--]--><!--[--><!--]--></a></div></div><div class="content" data-v-a0fd61f4><div class="curtain" data-v-a0fd61f4></div><div class="content-body" data-v-a0fd61f4><!--[--><!--]--><div class="VPNavBarSearch search" style="--vp-meta-key:&#39;Meta&#39;;" data-v-a0fd61f4><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><svg class="DocSearch-Search-Icon" width="20" height="20" viewBox="0 0 20 20" aria-label="search icon"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-a0fd61f4 data-v-7f418b0f><span id="main-nav-aria-label" class="visually-hidden" data-v-7f418b0f>Main Navigation</span><!--[--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-9c007e85><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-9c007e85><span class="text" data-v-9c007e85><!----><span data-v-9c007e85>大前端</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-9c007e85><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-9c007e85><div class="VPMenu" data-v-9c007e85 data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/webFrontEnd/md/01.html" data-v-43f1e123><!--[-->Web前端<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/uniapp/" data-v-43f1e123><!--[-->Uni-app<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/weChatMiniProgram/" data-v-43f1e123><!--[-->微信小程序<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/electron/" data-v-43f1e123><!--[-->Electron<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/flutter/" data-v-43f1e123><!--[-->Flutter<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/interview/01.html" data-v-43f1e123><!--[-->前端面试题<!--]--></a></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="./backEnd/" tabindex="0" data-v-7f418b0f data-v-42ef59de><!--[--><span data-v-42ef59de>后端</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="./linux/" tabindex="0" data-v-7f418b0f data-v-42ef59de><!--[--><span data-v-42ef59de>Linux</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="./myTeam/" tabindex="0" data-v-7f418b0f data-v-42ef59de><!--[--><span data-v-42ef59de>我的团队</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-a0fd61f4 data-v-f6a63727><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-f6a63727 data-v-ce54a7d1 data-v-b1685198><span class="check" data-v-b1685198><span class="icon" data-v-b1685198><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-ce54a7d1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-ce54a7d1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-a0fd61f4 data-v-0394ad82 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://gitee.com/luoguangguang" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="..." aria-label="cool link" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Dribbble</title><path d="M12...6.38z"/></svg></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-a0fd61f4 data-v-40855f84 data-v-9c007e85><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-9c007e85><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-9c007e85><circle cx="12" cy="12" r="2"></circle><circle cx="19" cy="12" r="2"></circle><circle cx="5" cy="12" r="2"></circle></svg></button><div class="menu" data-v-9c007e85><div class="VPMenu" data-v-9c007e85 data-v-e7ea1737><!----><!--[--><!--[--><!----><div class="group" data-v-40855f84><div class="item appearance" data-v-40855f84><p class="label" data-v-40855f84>Appearance</p><div class="appearance-action" data-v-40855f84><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-40855f84 data-v-ce54a7d1 data-v-b1685198><span class="check" data-v-b1685198><span class="icon" data-v-b1685198><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-ce54a7d1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-ce54a7d1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div></div></div><div class="group" data-v-40855f84><div class="item social-links" data-v-40855f84><div class="VPSocialLinks social-links-list" data-v-40855f84 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://gitee.com/luoguangguang" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="..." aria-label="cool link" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Dribbble</title><path d="M12...6.38z"/></svg></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-a0fd61f4 data-v-e5dd9c1c><span class="container" data-v-e5dd9c1c><span class="top" data-v-e5dd9c1c></span><span class="middle" data-v-e5dd9c1c></span><span class="bottom" data-v-e5dd9c1c></span></span></button></div></div></div></div><!----></header><div class="VPLocalNav reached-top" data-v-1919c326 data-v-79c8c1df><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-79c8c1df><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="menu-icon" data-v-79c8c1df><path d="M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"></path><path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"></path><path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"></path><path d="M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"></path></svg><span class="menu-text" data-v-79c8c1df>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-79c8c1df data-v-1c15a60a><button data-v-1c15a60a>Return to top</button><!----></div></div><aside class="VPSidebar" data-v-1919c326 data-v-b00e2fdd><div class="curtain" data-v-b00e2fdd></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-b00e2fdd><span class="visually-hidden" id="sidebar-aria-label" data-v-b00e2fdd> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="group" data-v-b00e2fdd><section class="VPSidebarItem level-0 collapsible" data-v-b00e2fdd data-v-e31bd47b><div class="item" role="button" tabindex="0" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><h2 class="text" data-v-e31bd47b>Flutter入门到实战</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-e31bd47b><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-e31bd47b><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-e31bd47b><!--[--><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter1/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第一章 初识Flutter</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter2/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第二章 简介</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter3/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第三章 基础组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter4/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第四章 布局类组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter5/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第五章 容器类Widget</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter6/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第六章 可滚动组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter7/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第七章 功能型Widget简介</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter8/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第八章 事件处理与通知</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter9/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第九章 动画</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter10/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十章 自定义组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter11/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十一章 文件操作</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter12/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十二章 Flutter 扩展</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter13/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十三章 多语言</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter14/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十四章 高级进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-1919c326 data-v-669faec9><div class="VPDoc has-sidebar has-aside" data-v-669faec9 data-v-6b87e69f><!--[--><!--]--><div class="container" data-v-6b87e69f><div class="aside" data-v-6b87e69f><div class="aside-curtain" data-v-6b87e69f></div><div class="aside-container" data-v-6b87e69f><div class="aside-content" data-v-6b87e69f><div class="VPDocAside" data-v-6b87e69f data-v-3f215769><!--[--><!--]--><!--[--><!--]--><div class="VPDocAsideOutline" role="navigation" data-v-3f215769 data-v-6ae8e080><div class="content" data-v-6ae8e080><div class="outline-marker" data-v-6ae8e080></div><div class="outline-title" role="heading" data-v-6ae8e080>On this page</div><nav aria-labelledby="doc-outline-aria-label" data-v-6ae8e080><span class="visually-hidden" id="doc-outline-aria-label" data-v-6ae8e080> Table of Contents for current page </span><ul class="root" data-v-6ae8e080 data-v-d0ee3533><!--[--><!--]--></ul></nav></div></div><!--[--><!--]--><div class="spacer" data-v-3f215769></div><!--[--><!--]--><div class="VPDocAsideCarbonAds" data-v-3f215769><div class="VPCarbonAds" data-v-2e1efd59></div></div><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-6b87e69f><div class="content-container" data-v-6b87e69f><!--[--><!--]--><!----><main class="main" data-v-6b87e69f><div style="position:relative;" class="vp-doc _largeFrontEnd_flutter_chapter10_watermark" data-v-6b87e69f><div><h1 id="_10-8-水印实例-文本绘制与离屏渲染" tabindex="-1">10.8 水印实例: 文本绘制与离屏渲染 <a class="header-anchor" href="#_10-8-水印实例-文本绘制与离屏渲染" aria-label="Permalink to &quot;10.8 水印实例: 文本绘制与离屏渲染&quot;">​</a></h1><p>本节将通过实现一个水印组件来介绍一下如何绘制文本以及如何进行离屏渲染。</p><p>在实际场景中，大多数情况下水印是要铺满整个屏幕的，如果不需要铺满屏幕，通常直接用组件组合即可实现，本节我们主要讨论的是需要铺满屏幕的水印。</p><h2 id="_10-8-1-水印组件watermark" tabindex="-1">10.8.1 水印组件WaterMark <a class="header-anchor" href="#_10-8-1-水印组件watermark" aria-label="Permalink to &quot;10.8.1 水印组件WaterMark&quot;">​</a></h2><p>我们可以通过绘制一个“单元水印”，然后让它在整个水印组件的背景中重复即可实现我们期望的功能，因此我们可以直接使用 DecoratedBox ，它拥有背景图重复功能。重复的问题解决后，那么主要的问题便是如何绘制单元水印，为了灵活好扩展，我们定义一个水印画笔接口，这样一来我们可以预置一些常用的画笔实现来满足大多数场景，同时如果开发者有自定义需求的话也可以通过自定义画笔来实现。</p><p>下面是水印组件 WaterMark 的定义：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">StatefulWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.repeat </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ImageRepeat</span><span style="color:#E1E4E8;">.repeat,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.painter,</span></span>
<span class="line"><span style="color:#E1E4E8;">  }) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">/// 单元水印画笔</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMarkPainter</span><span style="color:#E1E4E8;"> painter;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">/// 单元水印的重复方式</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ImageRepeat</span><span style="color:#E1E4E8;"> repeat;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">State</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;">&gt; </span><span style="color:#B392F0;">createState</span><span style="color:#E1E4E8;">() </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">_WaterMarkState</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">StatefulWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.repeat </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ImageRepeat</span><span style="color:#24292E;">.repeat,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.painter,</span></span>
<span class="line"><span style="color:#24292E;">  }) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">/// 单元水印画笔</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMarkPainter</span><span style="color:#24292E;"> painter;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">/// 单元水印的重复方式</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ImageRepeat</span><span style="color:#24292E;"> repeat;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">State</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;">&gt; </span><span style="color:#6F42C1;">createState</span><span style="color:#24292E;">() </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">_WaterMarkState</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>下面看一下 State 实现：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">_WaterMarkState</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">State</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;">&gt; {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">late</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Future</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">MemoryImage</span><span style="color:#E1E4E8;">&gt; _memoryImageFuture;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">initState</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 缓存的是promise</span></span>
<span class="line"><span style="color:#E1E4E8;">    _memoryImageFuture </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">_getWaterMarkImage</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">initState</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SizedBox</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">expand</span><span style="color:#E1E4E8;">( </span><span style="color:#6A737D;">// 水印尽可能大</span></span>
<span class="line"><span style="color:#E1E4E8;">      child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">FutureBuilder</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        future</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> _memoryImageFuture,</span></span>
<span class="line"><span style="color:#E1E4E8;">        builder</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> (</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context, </span><span style="color:#79B8FF;">AsyncSnapshot</span><span style="color:#E1E4E8;"> snapshot) {</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (snapshot.connectionState </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ConnectionState</span><span style="color:#E1E4E8;">.done) {</span></span>
<span class="line"><span style="color:#E1E4E8;">            </span><span style="color:#6A737D;">// 如果单元水印还没有绘制好先返回一个空的Container</span></span>
<span class="line"><span style="color:#E1E4E8;">            </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Container</span><span style="color:#E1E4E8;">(); </span></span>
<span class="line"><span style="color:#E1E4E8;">          } </span><span style="color:#F97583;">else</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">            </span><span style="color:#6A737D;">// 如果单元水印已经绘制好，则渲染水印</span></span>
<span class="line"><span style="color:#E1E4E8;">            </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">DecoratedBox</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">              decoration</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BoxDecoration</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">                image</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">DecorationImage</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">                  image</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> snapshot.data, </span><span style="color:#6A737D;">// 背景图，即我们绘制的单元水印图片</span></span>
<span class="line"><span style="color:#E1E4E8;">                  repeat</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> widget.repeat, </span><span style="color:#6A737D;">// 指定重复方式</span></span>
<span class="line"><span style="color:#E1E4E8;">                  alignment</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Alignment</span><span style="color:#E1E4E8;">.topLeft,</span></span>
<span class="line"><span style="color:#E1E4E8;">                ),</span></span>
<span class="line"><span style="color:#E1E4E8;">              ),</span></span>
<span class="line"><span style="color:#E1E4E8;">            );</span></span>
<span class="line"><span style="color:#E1E4E8;">          }</span></span>
<span class="line"><span style="color:#E1E4E8;">        },</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">didUpdateWidget</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;"> oldWidget) {</span></span>
<span class="line"><span style="color:#E1E4E8;">   ... </span><span style="color:#6A737D;">//待实现</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 离屏绘制单元水印并将绘制结果转为图片缓存起来</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Future</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">MemoryImage</span><span style="color:#E1E4E8;">&gt; </span><span style="color:#B392F0;">_getWaterMarkImage</span><span style="color:#E1E4E8;">() </span><span style="color:#F97583;">async</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">    ... </span><span style="color:#6A737D;">//待实现</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">dispose</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">   ...</span><span style="color:#6A737D;">// 待实现</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">_WaterMarkState</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">State</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;">&gt; {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">late</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Future</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">MemoryImage</span><span style="color:#24292E;">&gt; _memoryImageFuture;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">initState</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 缓存的是promise</span></span>
<span class="line"><span style="color:#24292E;">    _memoryImageFuture </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">_getWaterMarkImage</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">initState</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SizedBox</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">expand</span><span style="color:#24292E;">( </span><span style="color:#6A737D;">// 水印尽可能大</span></span>
<span class="line"><span style="color:#24292E;">      child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">FutureBuilder</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        future</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> _memoryImageFuture,</span></span>
<span class="line"><span style="color:#24292E;">        builder</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> (</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context, </span><span style="color:#005CC5;">AsyncSnapshot</span><span style="color:#24292E;"> snapshot) {</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (snapshot.connectionState </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ConnectionState</span><span style="color:#24292E;">.done) {</span></span>
<span class="line"><span style="color:#24292E;">            </span><span style="color:#6A737D;">// 如果单元水印还没有绘制好先返回一个空的Container</span></span>
<span class="line"><span style="color:#24292E;">            </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Container</span><span style="color:#24292E;">(); </span></span>
<span class="line"><span style="color:#24292E;">          } </span><span style="color:#D73A49;">else</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">            </span><span style="color:#6A737D;">// 如果单元水印已经绘制好，则渲染水印</span></span>
<span class="line"><span style="color:#24292E;">            </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">DecoratedBox</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">              decoration</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BoxDecoration</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">                image</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">DecorationImage</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">                  image</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> snapshot.data, </span><span style="color:#6A737D;">// 背景图，即我们绘制的单元水印图片</span></span>
<span class="line"><span style="color:#24292E;">                  repeat</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> widget.repeat, </span><span style="color:#6A737D;">// 指定重复方式</span></span>
<span class="line"><span style="color:#24292E;">                  alignment</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Alignment</span><span style="color:#24292E;">.topLeft,</span></span>
<span class="line"><span style="color:#24292E;">                ),</span></span>
<span class="line"><span style="color:#24292E;">              ),</span></span>
<span class="line"><span style="color:#24292E;">            );</span></span>
<span class="line"><span style="color:#24292E;">          }</span></span>
<span class="line"><span style="color:#24292E;">        },</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">didUpdateWidget</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;"> oldWidget) {</span></span>
<span class="line"><span style="color:#24292E;">   ... </span><span style="color:#6A737D;">//待实现</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 离屏绘制单元水印并将绘制结果转为图片缓存起来</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Future</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">MemoryImage</span><span style="color:#24292E;">&gt; </span><span style="color:#6F42C1;">_getWaterMarkImage</span><span style="color:#24292E;">() </span><span style="color:#D73A49;">async</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">    ... </span><span style="color:#6A737D;">//待实现</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">dispose</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">   ...</span><span style="color:#6A737D;">// 待实现</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>我们通过 DecoratedBox 来实现背景图重复，同时我们在组件初始化时开始进行离屏绘制单元水印，并将结果缓存在 MemoryImage 中，因为离屏绘制是一个异步任务，所以直接缓存 Future 即可。这里需要注意，当组件重新build时，如果画笔配置发生变化，则我们需要重新绘制单元水印并缓存新的绘制结果：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">didUpdateWidget</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;"> oldWidget) {</span></span>
<span class="line"><span style="color:#E1E4E8;">     </span><span style="color:#6A737D;">// 如果画笔发生了变化（类型或者配置）则重新绘制水印</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (widget.painter.runtimeType </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> oldWidget.painter.runtimeType </span><span style="color:#F97583;">||</span></span>
<span class="line"><span style="color:#E1E4E8;">        widget.painter.</span><span style="color:#B392F0;">shouldRepaint</span><span style="color:#E1E4E8;">(oldWidget.painter)) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">//先释放之前的缓存</span></span>
<span class="line"><span style="color:#E1E4E8;">      _memoryImageFuture.</span><span style="color:#B392F0;">then</span><span style="color:#E1E4E8;">((value) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> value.</span><span style="color:#B392F0;">evict</span><span style="color:#E1E4E8;">());</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">//重新绘制并缓存</span></span>
<span class="line"><span style="color:#E1E4E8;">      _memoryImageFuture </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">_getWaterMarkImage</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">didUpdateWidget</span><span style="color:#E1E4E8;">(oldWidget);  </span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">didUpdateWidget</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;"> oldWidget) {</span></span>
<span class="line"><span style="color:#24292E;">     </span><span style="color:#6A737D;">// 如果画笔发生了变化（类型或者配置）则重新绘制水印</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (widget.painter.runtimeType </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> oldWidget.painter.runtimeType </span><span style="color:#D73A49;">||</span></span>
<span class="line"><span style="color:#24292E;">        widget.painter.</span><span style="color:#6F42C1;">shouldRepaint</span><span style="color:#24292E;">(oldWidget.painter)) {</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">//先释放之前的缓存</span></span>
<span class="line"><span style="color:#24292E;">      _memoryImageFuture.</span><span style="color:#6F42C1;">then</span><span style="color:#24292E;">((value) </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> value.</span><span style="color:#6F42C1;">evict</span><span style="color:#24292E;">());</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">//重新绘制并缓存</span></span>
<span class="line"><span style="color:#24292E;">      _memoryImageFuture </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">_getWaterMarkImage</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">didUpdateWidget</span><span style="color:#24292E;">(oldWidget);  </span></span>
<span class="line"><span style="color:#24292E;">  }</span></span></code></pre></div><p>注意，在重新绘制单元水印之前要先将旧单元水印的缓存清理掉，清理缓存可以通过调用 MemoryImage 的 evict 方法。同时，当组件卸载时，我们也要释放缓存：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">dispose</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//释放图片缓存</span></span>
<span class="line"><span style="color:#E1E4E8;">    _memoryImageFuture.</span><span style="color:#B392F0;">then</span><span style="color:#E1E4E8;">((value) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> value.</span><span style="color:#B392F0;">evict</span><span style="color:#E1E4E8;">());</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">dispose</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">dispose</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//释放图片缓存</span></span>
<span class="line"><span style="color:#24292E;">    _memoryImageFuture.</span><span style="color:#6F42C1;">then</span><span style="color:#24292E;">((value) </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> value.</span><span style="color:#6F42C1;">evict</span><span style="color:#24292E;">());</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">dispose</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span></code></pre></div><p>接下来就需要重新绘制单元水印了，调用 <code>_getWaterMarkImage() </code> 方法即可，该方法的功能是离屏绘制单元水印并将绘制结果转为图片缓存起来，下面我们看一下它的实现。</p><h3 id="离屏绘制" tabindex="-1">离屏绘制 <a class="header-anchor" href="#离屏绘制" aria-label="Permalink to &quot;离屏绘制&quot;">​</a></h3><p>离屏绘制的代码如下：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#6A737D;">// 离屏绘制单元水印并将绘制结果保存为图片缓存起来</span></span>
<span class="line"><span style="color:#79B8FF;">Future</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">MemoryImage</span><span style="color:#E1E4E8;">&gt; </span><span style="color:#B392F0;">_getWaterMarkImage</span><span style="color:#E1E4E8;">() </span><span style="color:#F97583;">async</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 创建一个 Canvas 进行离屏绘制，细节和原理请查看本书后面14.5节。</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> recorder </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> ui.</span><span style="color:#79B8FF;">PictureRecorder</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> canvas </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Canvas</span><span style="color:#E1E4E8;">(recorder);</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 绘制单元水印并获取其大小</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> size </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> widget.painter.</span><span style="color:#B392F0;">paintUnit</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    canvas,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">MediaQueryData</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">fromWindow</span><span style="color:#E1E4E8;">(ui.window).devicePixelRatio,</span></span>
<span class="line"><span style="color:#E1E4E8;">  );</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> picture </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> recorder.</span><span style="color:#B392F0;">endRecording</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//将单元水印导为图片并缓存起来</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> img </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">await</span><span style="color:#E1E4E8;"> picture.</span><span style="color:#B392F0;">toImage</span><span style="color:#E1E4E8;">(size.width.</span><span style="color:#B392F0;">ceil</span><span style="color:#E1E4E8;">(), size.height.</span><span style="color:#B392F0;">ceil</span><span style="color:#E1E4E8;">());</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> byteData </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">await</span><span style="color:#E1E4E8;"> img.</span><span style="color:#B392F0;">toByteData</span><span style="color:#E1E4E8;">(format</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> ui.</span><span style="color:#79B8FF;">ImageByteFormat</span><span style="color:#E1E4E8;">.png);</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> pngBytes </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> byteData</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.buffer.</span><span style="color:#B392F0;">asUint8List</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">MemoryImage</span><span style="color:#E1E4E8;">(pngBytes);</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#6A737D;">// 离屏绘制单元水印并将绘制结果保存为图片缓存起来</span></span>
<span class="line"><span style="color:#005CC5;">Future</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">MemoryImage</span><span style="color:#24292E;">&gt; </span><span style="color:#6F42C1;">_getWaterMarkImage</span><span style="color:#24292E;">() </span><span style="color:#D73A49;">async</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 创建一个 Canvas 进行离屏绘制，细节和原理请查看本书后面14.5节。</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> recorder </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> ui.</span><span style="color:#005CC5;">PictureRecorder</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> canvas </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Canvas</span><span style="color:#24292E;">(recorder);</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 绘制单元水印并获取其大小</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> size </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> widget.painter.</span><span style="color:#6F42C1;">paintUnit</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    canvas,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">MediaQueryData</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">fromWindow</span><span style="color:#24292E;">(ui.window).devicePixelRatio,</span></span>
<span class="line"><span style="color:#24292E;">  );</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> picture </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> recorder.</span><span style="color:#6F42C1;">endRecording</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//将单元水印导为图片并缓存起来</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> img </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">await</span><span style="color:#24292E;"> picture.</span><span style="color:#6F42C1;">toImage</span><span style="color:#24292E;">(size.width.</span><span style="color:#6F42C1;">ceil</span><span style="color:#24292E;">(), size.height.</span><span style="color:#6F42C1;">ceil</span><span style="color:#24292E;">());</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> byteData </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">await</span><span style="color:#24292E;"> img.</span><span style="color:#6F42C1;">toByteData</span><span style="color:#24292E;">(format</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> ui.</span><span style="color:#005CC5;">ImageByteFormat</span><span style="color:#24292E;">.png);</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> pngBytes </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> byteData</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.buffer.</span><span style="color:#6F42C1;">asUint8List</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">MemoryImage</span><span style="color:#24292E;">(pngBytes);</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>我们通过手动创建了一个 Canvas 和一个 PictureRecorder 来实现离屏绘制，PictureRecorder 的功能先简单介绍一下，我们会在本书后面绘制原理相关章节详细介绍，简单来说：调用 Canvas API 后，实际上产生的是一系列绘制指令，这些绘制指令执行后才能获取绘制结果，而PictureRecorder 就是一个绘制指令记录器，它可以记录一段时间内所有绘制指令，我们可以通过调用 <code>recorder.endRecording()</code> 方法来获取记录的绘制指令，该方法返回一个 Picture 对象，它是绘制指令的载体，它有一个 toImage 方法，调用后会执行绘制指令获得绘制的像素结果（ui.Image 对象），之后我们就可以将像素结果转为 png 格式的数据并缓存在MemoryImage 中。</p><h2 id="_10-8-2-单元水印画笔" tabindex="-1">10.8.2 单元水印画笔 <a class="header-anchor" href="#_10-8-2-单元水印画笔" aria-label="Permalink to &quot;10.8.2 单元水印画笔&quot;">​</a></h2><p>现在我们看一下如何绘制单元水印，我们先看一下水印画笔接口的定义：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#6A737D;">/// 定义水印画笔</span></span>
<span class="line"><span style="color:#F97583;">abstract</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMarkPainter</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">/// 绘制&quot;单元水印&quot;，完整的水印是由单元水印重复平铺组成,返回值为&quot;单元水印&quot;占用空间的大小。</span></span>
<span class="line"><span style="color:#6A737D;">  /// </span><span style="color:#FFAB70;">[devicePixelRatio]</span><span style="color:#6A737D;">: 因为最终要将绘制内容保存为图片，所以在绘制时需要根据屏幕的</span></span>
<span class="line"><span style="color:#6A737D;">  /// DPR来放大，以防止失真</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">paintUnit</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Canvas</span><span style="color:#E1E4E8;"> canvas, </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> devicePixelRatio);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">/// 是否需要重绘</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">bool</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">shouldRepaint</span><span style="color:#E1E4E8;">(</span><span style="color:#F97583;">covariant</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMarkPainter</span><span style="color:#E1E4E8;"> oldPainter) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#6A737D;">/// 定义水印画笔</span></span>
<span class="line"><span style="color:#D73A49;">abstract</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMarkPainter</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">/// 绘制&quot;单元水印&quot;，完整的水印是由单元水印重复平铺组成,返回值为&quot;单元水印&quot;占用空间的大小。</span></span>
<span class="line"><span style="color:#6A737D;">  /// </span><span style="color:#E36209;">[devicePixelRatio]</span><span style="color:#6A737D;">: 因为最终要将绘制内容保存为图片，所以在绘制时需要根据屏幕的</span></span>
<span class="line"><span style="color:#6A737D;">  /// DPR来放大，以防止失真</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Size</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">paintUnit</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Canvas</span><span style="color:#24292E;"> canvas, </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> devicePixelRatio);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">/// 是否需要重绘</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">bool</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">shouldRepaint</span><span style="color:#24292E;">(</span><span style="color:#D73A49;">covariant</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMarkPainter</span><span style="color:#24292E;"> oldPainter) </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>定义很简单，就两个函数：</p><ul><li>paintUnit 用于绘制单元水印，这里需要注意一点，因为很多 UI 元素的大小只能在绘制时获取，无法提前知道大小，所以 <code>paintUnit</code> 在完成绘制单元水印任务的同时，最后得返回单元水印的大小信息，它在导为图片时要用到。</li><li>shouldRepaint：当画笔状态发生变化且会影响单元水印的外观时返回 true，否则返回 false，返回 true 后重绘单元水印。它是在 _WaterMarkState 的 didUpdateWidget 方法中调用，读者可以结合源码理解。</li></ul><h2 id="_10-8-3-文本水印画笔" tabindex="-1">10.8.3 文本水印画笔 <a class="header-anchor" href="#_10-8-3-文本水印画笔" aria-label="Permalink to &quot;10.8.3 文本水印画笔&quot;">​</a></h2><p>下面我们实现一个文本水印画笔，它可以绘制一段文本，我们可以指定文本的样式和旋转角度。</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#6A737D;">/// 文本水印画笔</span></span>
<span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextWaterMarkPainter</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMarkPainter</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">TextWaterMarkPainter</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> rotate,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">EdgeInsets</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> padding,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">TextStyle</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> textStyle,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.text,</span></span>
<span class="line"><span style="color:#E1E4E8;">  })  </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">assert</span><span style="color:#E1E4E8;">(rotate </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">null</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">||</span><span style="color:#E1E4E8;"> rotate </span><span style="color:#F97583;">&gt;=</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">90</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">&amp;&amp;</span><span style="color:#E1E4E8;"> rotate </span><span style="color:#F97583;">&lt;=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">90</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">        rotate </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> rotate </span><span style="color:#F97583;">??</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">        padding </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> padding </span><span style="color:#F97583;">??</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">EdgeInsets</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">all</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">10.0</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">        textStyle </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> textStyle </span><span style="color:#F97583;">??</span></span>
<span class="line"><span style="color:#E1E4E8;">            </span><span style="color:#79B8FF;">TextStyle</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">              color</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Color</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">fromARGB</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">20</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">              fontSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">14</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">            );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> rotate; </span><span style="color:#6A737D;">// 文本旋转的度数，是角度不是弧度</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">TextStyle</span><span style="color:#E1E4E8;"> textStyle; </span><span style="color:#6A737D;">// 文本样式</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">EdgeInsets</span><span style="color:#E1E4E8;"> padding; </span><span style="color:#6A737D;">// 文本的 padding</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">String</span><span style="color:#E1E4E8;"> text; </span><span style="color:#6A737D;">// 文本</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">paintUnit</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Canvas</span><span style="color:#E1E4E8;"> canvas,</span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> devicePixelRatio) {</span></span>
<span class="line"><span style="color:#E1E4E8;">   </span><span style="color:#6A737D;">// 1. 先绘制文本</span></span>
<span class="line"><span style="color:#E1E4E8;">   </span><span style="color:#6A737D;">// 2. 应用旋转和padding </span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">bool</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">shouldRepaint</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">TextWaterMarkPainter</span><span style="color:#E1E4E8;"> oldPainter) {</span></span>
<span class="line"><span style="color:#E1E4E8;">   ...</span><span style="color:#6A737D;">// 待实现</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#6A737D;">/// 文本水印画笔</span></span>
<span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextWaterMarkPainter</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMarkPainter</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">TextWaterMarkPainter</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> rotate,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">EdgeInsets</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> padding,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">TextStyle</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> textStyle,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.text,</span></span>
<span class="line"><span style="color:#24292E;">  })  </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">assert</span><span style="color:#24292E;">(rotate </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">null</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">||</span><span style="color:#24292E;"> rotate </span><span style="color:#D73A49;">&gt;=</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">90</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">&amp;&amp;</span><span style="color:#24292E;"> rotate </span><span style="color:#D73A49;">&lt;=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">90</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">        rotate </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> rotate </span><span style="color:#D73A49;">??</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">        padding </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> padding </span><span style="color:#D73A49;">??</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">EdgeInsets</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">all</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">10.0</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">        textStyle </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> textStyle </span><span style="color:#D73A49;">??</span></span>
<span class="line"><span style="color:#24292E;">            </span><span style="color:#005CC5;">TextStyle</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">              color</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Color</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">fromARGB</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">20</span><span style="color:#24292E;">, </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">, </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">, </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">              fontSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">14</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">            );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> rotate; </span><span style="color:#6A737D;">// 文本旋转的度数，是角度不是弧度</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">TextStyle</span><span style="color:#24292E;"> textStyle; </span><span style="color:#6A737D;">// 文本样式</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">EdgeInsets</span><span style="color:#24292E;"> padding; </span><span style="color:#6A737D;">// 文本的 padding</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">String</span><span style="color:#24292E;"> text; </span><span style="color:#6A737D;">// 文本</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Size</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">paintUnit</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Canvas</span><span style="color:#24292E;"> canvas,</span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> devicePixelRatio) {</span></span>
<span class="line"><span style="color:#24292E;">   </span><span style="color:#6A737D;">// 1. 先绘制文本</span></span>
<span class="line"><span style="color:#24292E;">   </span><span style="color:#6A737D;">// 2. 应用旋转和padding </span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">bool</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">shouldRepaint</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">TextWaterMarkPainter</span><span style="color:#24292E;"> oldPainter) {</span></span>
<span class="line"><span style="color:#24292E;">   ...</span><span style="color:#6A737D;">// 待实现</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>paintUnit 的绘制分两步：</p><ol><li>绘制文本</li><li>应用旋转和padding</li></ol><h3 id="绘制文本" tabindex="-1">绘制文本 <a class="header-anchor" href="#绘制文本" aria-label="Permalink to &quot;绘制文本&quot;">​</a></h3><p>文本的绘制三步：</p><ol><li>创建一个 ParagraphBuilder，记为 builder。</li><li>调用 builder.add 添加要绘制的字符串。</li><li>构建文本并进行 layout，因为在 layout 后才能知道文本所占用的空间。</li><li>调用 canvas.drawParagraph 绘制。</li></ol><p>具体代码如下：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">import</span><span style="color:#E1E4E8;"> </span><span style="color:#9ECBFF;">&#39;dart:ui&#39;</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">as</span><span style="color:#E1E4E8;"> ui;</span></span>
<span class="line"><span style="color:#E1E4E8;">...</span></span>
<span class="line"><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">paintUnit</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Canvas</span><span style="color:#E1E4E8;"> canvas,</span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> devicePixelRatio) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//根据屏幕 devicePixelRatio 对文本样式中长度相关的一些值乘以devicePixelRatio</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> _textStyle </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">_handleTextStyle</span><span style="color:#E1E4E8;">(textStyle, devicePixelRatio);</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> _padding </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> padding </span><span style="color:#F97583;">*</span><span style="color:#E1E4E8;"> devicePixelRatio;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//构建文本段落</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> builder </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> ui.</span><span style="color:#79B8FF;">ParagraphBuilder</span><span style="color:#E1E4E8;">(_textStyle.</span><span style="color:#B392F0;">getParagraphStyle</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      textDirection</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> textDirection,</span></span>
<span class="line"><span style="color:#E1E4E8;">      textAlign</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextAlign</span><span style="color:#E1E4E8;">.start,</span></span>
<span class="line"><span style="color:#E1E4E8;">      textScaleFactor</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> devicePixelRatio,</span></span>
<span class="line"><span style="color:#E1E4E8;">    ));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//添加要绘制的文本及样式</span></span>
<span class="line"><span style="color:#E1E4E8;">    builder</span></span>
<span class="line"><span style="color:#E1E4E8;">      ..</span><span style="color:#B392F0;">pushStyle</span><span style="color:#E1E4E8;">(_textStyle.</span><span style="color:#B392F0;">getTextStyle</span><span style="color:#E1E4E8;">()) </span><span style="color:#6A737D;">// textStyle 为 ui.TextStyle</span></span>
<span class="line"><span style="color:#E1E4E8;">      ..</span><span style="color:#B392F0;">addText</span><span style="color:#E1E4E8;">(text);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//layout 后我们才能知道文本占用的空间</span></span>
<span class="line"><span style="color:#E1E4E8;">    ui.</span><span style="color:#79B8FF;">Paragraph</span><span style="color:#E1E4E8;"> paragraph </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> builder.</span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"><span style="color:#E1E4E8;">      ..</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(ui.</span><span style="color:#79B8FF;">ParagraphConstraints</span><span style="color:#E1E4E8;">(width</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;">.infinity));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//文本占用的真实宽度</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> textWidth </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> paragraph.longestLine.</span><span style="color:#B392F0;">ceilToDouble</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//文本占用的真实高度</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> fontSize </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> paragraph.height;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span></span>
<span class="line"><span style="color:#E1E4E8;">    ...</span><span style="color:#6A737D;">//省略应用旋转和 padding 的相关代码</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//绘制文本</span></span>
<span class="line"><span style="color:#E1E4E8;">    canvas.</span><span style="color:#B392F0;">drawParagraph</span><span style="color:#E1E4E8;">(paragraph, </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;">.zero);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">TextStyle</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">_handleTextStyle</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> devicePixelRatio) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">var</span><span style="color:#E1E4E8;"> style </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> textStyle;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">_scale</span><span style="color:#E1E4E8;">(attr) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> attr </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">null</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">1.0</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> devicePixelRatio;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> style.</span><span style="color:#B392F0;">apply</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      decorationThicknessFactor</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">_scale</span><span style="color:#E1E4E8;">(style.decorationThickness),</span></span>
<span class="line"><span style="color:#E1E4E8;">      letterSpacingFactor</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">_scale</span><span style="color:#E1E4E8;">(style.letterSpacing),</span></span>
<span class="line"><span style="color:#E1E4E8;">      wordSpacingFactor</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">_scale</span><span style="color:#E1E4E8;">(style.wordSpacing),</span></span>
<span class="line"><span style="color:#E1E4E8;">      heightFactor</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">_scale</span><span style="color:#E1E4E8;">(style.height),</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">import</span><span style="color:#24292E;"> </span><span style="color:#032F62;">&#39;dart:ui&#39;</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">as</span><span style="color:#24292E;"> ui;</span></span>
<span class="line"><span style="color:#24292E;">...</span></span>
<span class="line"><span style="color:#24292E;"> </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Size</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">paintUnit</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Canvas</span><span style="color:#24292E;"> canvas,</span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> devicePixelRatio) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//根据屏幕 devicePixelRatio 对文本样式中长度相关的一些值乘以devicePixelRatio</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> _textStyle </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">_handleTextStyle</span><span style="color:#24292E;">(textStyle, devicePixelRatio);</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> _padding </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> padding </span><span style="color:#D73A49;">*</span><span style="color:#24292E;"> devicePixelRatio;</span></span>
<span class="line"><span style="color:#24292E;">  </span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//构建文本段落</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> builder </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> ui.</span><span style="color:#005CC5;">ParagraphBuilder</span><span style="color:#24292E;">(_textStyle.</span><span style="color:#6F42C1;">getParagraphStyle</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      textDirection</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> textDirection,</span></span>
<span class="line"><span style="color:#24292E;">      textAlign</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextAlign</span><span style="color:#24292E;">.start,</span></span>
<span class="line"><span style="color:#24292E;">      textScaleFactor</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> devicePixelRatio,</span></span>
<span class="line"><span style="color:#24292E;">    ));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//添加要绘制的文本及样式</span></span>
<span class="line"><span style="color:#24292E;">    builder</span></span>
<span class="line"><span style="color:#24292E;">      ..</span><span style="color:#6F42C1;">pushStyle</span><span style="color:#24292E;">(_textStyle.</span><span style="color:#6F42C1;">getTextStyle</span><span style="color:#24292E;">()) </span><span style="color:#6A737D;">// textStyle 为 ui.TextStyle</span></span>
<span class="line"><span style="color:#24292E;">      ..</span><span style="color:#6F42C1;">addText</span><span style="color:#24292E;">(text);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//layout 后我们才能知道文本占用的空间</span></span>
<span class="line"><span style="color:#24292E;">    ui.</span><span style="color:#005CC5;">Paragraph</span><span style="color:#24292E;"> paragraph </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> builder.</span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">()</span></span>
<span class="line"><span style="color:#24292E;">      ..</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(ui.</span><span style="color:#005CC5;">ParagraphConstraints</span><span style="color:#24292E;">(width</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">double</span><span style="color:#24292E;">.infinity));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//文本占用的真实宽度</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> textWidth </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> paragraph.longestLine.</span><span style="color:#6F42C1;">ceilToDouble</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//文本占用的真实高度</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> fontSize </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> paragraph.height;</span></span>
<span class="line"><span style="color:#24292E;">    </span></span>
<span class="line"><span style="color:#24292E;">    ...</span><span style="color:#6A737D;">//省略应用旋转和 padding 的相关代码</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//绘制文本</span></span>
<span class="line"><span style="color:#24292E;">    canvas.</span><span style="color:#6F42C1;">drawParagraph</span><span style="color:#24292E;">(paragraph, </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;">.zero);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">TextStyle</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">_handleTextStyle</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> devicePixelRatio) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">var</span><span style="color:#24292E;"> style </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> textStyle;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">_scale</span><span style="color:#24292E;">(attr) </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> attr </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">null</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">1.0</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> devicePixelRatio;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> style.</span><span style="color:#6F42C1;">apply</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      decorationThicknessFactor</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">_scale</span><span style="color:#24292E;">(style.decorationThickness),</span></span>
<span class="line"><span style="color:#24292E;">      letterSpacingFactor</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">_scale</span><span style="color:#24292E;">(style.letterSpacing),</span></span>
<span class="line"><span style="color:#24292E;">      wordSpacingFactor</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">_scale</span><span style="color:#24292E;">(style.wordSpacing),</span></span>
<span class="line"><span style="color:#24292E;">      heightFactor</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">_scale</span><span style="color:#24292E;">(style.height),</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span></code></pre></div><p>可以看到绘制文本的过程还是比较复杂的，为此 Flutter 提供了一个专门用于绘制文本的画笔 TextPainter，我们用 TextPainter 改造上面代码：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#6A737D;">//构建文本画笔</span></span>
<span class="line"><span style="color:#79B8FF;">TextPainter</span><span style="color:#E1E4E8;"> painter </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextPainter</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">  textDirection</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextDirection</span><span style="color:#E1E4E8;">.ltr,</span></span>
<span class="line"><span style="color:#E1E4E8;">  textScaleFactor</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> devicePixelRatio,</span></span>
<span class="line"><span style="color:#E1E4E8;">);</span></span>
<span class="line"><span style="color:#6A737D;">//添加文本和样式</span></span>
<span class="line"><span style="color:#E1E4E8;">painter.text </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextSpan</span><span style="color:#E1E4E8;">(text</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> text, style</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> _textStyle);</span></span>
<span class="line"><span style="color:#6A737D;">//对文本进行布局</span></span>
<span class="line"><span style="color:#E1E4E8;">painter.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">//文本占用的真实宽度</span></span>
<span class="line"><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> textWidth </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> painter.width;</span></span>
<span class="line"><span style="color:#6A737D;">//文本占用的真实高度</span></span>
<span class="line"><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> textHeight </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> painter.height;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;"> ...</span><span style="color:#6A737D;">//省略应用旋转和 padding 的相关代码</span></span>
<span class="line"><span style="color:#E1E4E8;">   </span></span>
<span class="line"><span style="color:#6A737D;">// 绘制文本</span></span>
<span class="line"><span style="color:#E1E4E8;">painter.</span><span style="color:#B392F0;">paint</span><span style="color:#E1E4E8;">(canvas, </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;">.zero);</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#6A737D;">//构建文本画笔</span></span>
<span class="line"><span style="color:#005CC5;">TextPainter</span><span style="color:#24292E;"> painter </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextPainter</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">  textDirection</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextDirection</span><span style="color:#24292E;">.ltr,</span></span>
<span class="line"><span style="color:#24292E;">  textScaleFactor</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> devicePixelRatio,</span></span>
<span class="line"><span style="color:#24292E;">);</span></span>
<span class="line"><span style="color:#6A737D;">//添加文本和样式</span></span>
<span class="line"><span style="color:#24292E;">painter.text </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextSpan</span><span style="color:#24292E;">(text</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> text, style</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> _textStyle);</span></span>
<span class="line"><span style="color:#6A737D;">//对文本进行布局</span></span>
<span class="line"><span style="color:#24292E;">painter.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">//文本占用的真实宽度</span></span>
<span class="line"><span style="color:#D73A49;">final</span><span style="color:#24292E;"> textWidth </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> painter.width;</span></span>
<span class="line"><span style="color:#6A737D;">//文本占用的真实高度</span></span>
<span class="line"><span style="color:#D73A49;">final</span><span style="color:#24292E;"> textHeight </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> painter.height;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;"> ...</span><span style="color:#6A737D;">//省略应用旋转和 padding 的相关代码</span></span>
<span class="line"><span style="color:#24292E;">   </span></span>
<span class="line"><span style="color:#6A737D;">// 绘制文本</span></span>
<span class="line"><span style="color:#24292E;">painter.</span><span style="color:#6F42C1;">paint</span><span style="color:#24292E;">(canvas, </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;">.zero);</span></span></code></pre></div><p>可以看到，代码实际上少不了多少，但是清晰了一些。</p><p>另外 TextPainter 在实战中还有一个用处就是我们想提前知道 Text 组件的宽高时，可以通过 TextPainter 来提前测量，比如：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">wTextPainterTest</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 我们想提前知道 Text 组件的大小</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Text</span><span style="color:#E1E4E8;"> text </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Text</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;flutter@wendux&#39;</span><span style="color:#E1E4E8;">, style</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextStyle</span><span style="color:#E1E4E8;">(fontSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">18</span><span style="color:#E1E4E8;">));</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 使用 TextPainter 来测量</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">TextPainter</span><span style="color:#E1E4E8;"> painter </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextPainter</span><span style="color:#E1E4E8;">(textDirection</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextDirection</span><span style="color:#E1E4E8;">.ltr);</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 将 Text 组件文本和样式透传给TextPainter</span></span>
<span class="line"><span style="color:#E1E4E8;">    painter.text </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextSpan</span><span style="color:#E1E4E8;">(text</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> text.data,style</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;">text.style);</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 开始布局测量，调用 layout 后就能获取文本大小了</span></span>
<span class="line"><span style="color:#E1E4E8;">    painter.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 自定义组件 AfterLayout 可以在布局结束后获取子组件的大小，我们用它来验证一下</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// TextPainter 测量的宽高是否正确</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">AfterLayout</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      callback</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> (</span><span style="color:#79B8FF;">RenderAfterLayout</span><span style="color:#E1E4E8;"> value) {</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#6A737D;">// 输出日志</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#B392F0;">print</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;text size(painter): ${</span><span style="color:#79B8FF;">painter</span><span style="color:#9ECBFF;">.</span><span style="color:#79B8FF;">size</span><span style="color:#9ECBFF;">}&#39;</span><span style="color:#E1E4E8;">);</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#B392F0;">print</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;text size(after layout): ${</span><span style="color:#79B8FF;">value</span><span style="color:#9ECBFF;">.</span><span style="color:#79B8FF;">size</span><span style="color:#9ECBFF;">}&#39;</span><span style="color:#E1E4E8;">);</span></span>
<span class="line"><span style="color:#E1E4E8;">      },</span></span>
<span class="line"><span style="color:#E1E4E8;">      child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> text,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">wTextPainterTest</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 我们想提前知道 Text 组件的大小</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Text</span><span style="color:#24292E;"> text </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Text</span><span style="color:#24292E;">(</span><span style="color:#032F62;">&#39;flutter@wendux&#39;</span><span style="color:#24292E;">, style</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextStyle</span><span style="color:#24292E;">(fontSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">18</span><span style="color:#24292E;">));</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 使用 TextPainter 来测量</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">TextPainter</span><span style="color:#24292E;"> painter </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextPainter</span><span style="color:#24292E;">(textDirection</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextDirection</span><span style="color:#24292E;">.ltr);</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 将 Text 组件文本和样式透传给TextPainter</span></span>
<span class="line"><span style="color:#24292E;">    painter.text </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextSpan</span><span style="color:#24292E;">(text</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> text.data,style</span><span style="color:#D73A49;">:</span><span style="color:#24292E;">text.style);</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 开始布局测量，调用 layout 后就能获取文本大小了</span></span>
<span class="line"><span style="color:#24292E;">    painter.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 自定义组件 AfterLayout 可以在布局结束后获取子组件的大小，我们用它来验证一下</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// TextPainter 测量的宽高是否正确</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">AfterLayout</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      callback</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> (</span><span style="color:#005CC5;">RenderAfterLayout</span><span style="color:#24292E;"> value) {</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6A737D;">// 输出日志</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6F42C1;">print</span><span style="color:#24292E;">(</span><span style="color:#032F62;">&#39;text size(painter): ${</span><span style="color:#005CC5;">painter</span><span style="color:#032F62;">.</span><span style="color:#005CC5;">size</span><span style="color:#032F62;">}&#39;</span><span style="color:#24292E;">);</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6F42C1;">print</span><span style="color:#24292E;">(</span><span style="color:#032F62;">&#39;text size(after layout): ${</span><span style="color:#005CC5;">value</span><span style="color:#032F62;">.</span><span style="color:#005CC5;">size</span><span style="color:#032F62;">}&#39;</span><span style="color:#24292E;">);</span></span>
<span class="line"><span style="color:#24292E;">      },</span></span>
<span class="line"><span style="color:#24292E;">      child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> text,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span></code></pre></div><p>运行后如图10-9：</p><p><img src="/assets/10-9.fd6a5cab.png" alt="图10-9"></p><p>从日志可以看到通过 TextPainter 测量的文本大小和实际占用是</p><h3 id="应用旋转和-padding" tabindex="-1">应用旋转和 padding <a class="header-anchor" href="#应用旋转和-padding" aria-label="Permalink to &quot;应用旋转和 padding&quot;">​</a></h3><p>应用旋转效果本身比较简单，但难的是文本旋转后它占用的空间大小会发生变化，所以我们得动态计算旋转后文本所占用空间的大小，假设沿顺时针方向旋转了了 rotate 角度，画出布局图10-10：</p><p><img src="/assets/10-10.fd8ca301.png" alt="图10-10"></p><p>我们可以根据上面公式求出最终的宽度和高度，是不是感觉高中学的三角函数终于派上用场了！注意，上面的公式中并没有考虑padding，padding 的处理比较简单，不赘述，看代码：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">paintUnit</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Canvas</span><span style="color:#E1E4E8;"> canvas, </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> devicePixelRatio) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    ... </span><span style="color:#6A737D;">// 省略</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//文本占用的真实宽度</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> textWidth </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> painter.width;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//文本占用的真实高度</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> textHeight </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> painter.height;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 将弧度转化为度数</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> radians </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> math.pi </span><span style="color:#F97583;">*</span><span style="color:#E1E4E8;"> rotate </span><span style="color:#F97583;">/</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">180</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//通过三角函数计算旋转后的位置和size</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> orgSin </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> math.</span><span style="color:#B392F0;">sin</span><span style="color:#E1E4E8;">(radians);</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> sin </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> orgSin.</span><span style="color:#B392F0;">abs</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> cos </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> math.</span><span style="color:#B392F0;">cos</span><span style="color:#E1E4E8;">(radians).</span><span style="color:#B392F0;">abs</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> width </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> textWidth </span><span style="color:#F97583;">*</span><span style="color:#E1E4E8;"> cos;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> height </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> textWidth </span><span style="color:#F97583;">*</span><span style="color:#E1E4E8;"> sin;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> adjustWidth </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> fontSize </span><span style="color:#F97583;">*</span><span style="color:#E1E4E8;"> sin;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> adjustHeight </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> fontSize </span><span style="color:#F97583;">*</span><span style="color:#E1E4E8;"> cos;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 为什么要平移？下面解释</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (orgSin </span><span style="color:#F97583;">&gt;=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">) { </span><span style="color:#6A737D;">// 旋转角度为正</span></span>
<span class="line"><span style="color:#E1E4E8;">      canvas.</span><span style="color:#B392F0;">translate</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        adjustWidth </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> padding.left,</span></span>
<span class="line"><span style="color:#E1E4E8;">        padding.top,</span></span>
<span class="line"><span style="color:#E1E4E8;">      );</span></span>
<span class="line"><span style="color:#E1E4E8;">    } </span><span style="color:#F97583;">else</span><span style="color:#E1E4E8;"> { </span><span style="color:#6A737D;">// 旋转角度为负</span></span>
<span class="line"><span style="color:#E1E4E8;">      canvas.</span><span style="color:#B392F0;">translate</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        padding.left,</span></span>
<span class="line"><span style="color:#E1E4E8;">        height </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> padding.top,</span></span>
<span class="line"><span style="color:#E1E4E8;">      );</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"><span style="color:#E1E4E8;">    canvas.</span><span style="color:#B392F0;">rotate</span><span style="color:#E1E4E8;">(radians);</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 绘制文本</span></span>
<span class="line"><span style="color:#E1E4E8;">    painter.</span><span style="color:#B392F0;">paint</span><span style="color:#E1E4E8;">(canvas, </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;">.zero);</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 返回水印单元所占的真实空间大小（需要加上padding）</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      width </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> adjustWidth </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> padding.horizontal,</span></span>
<span class="line"><span style="color:#E1E4E8;">      height </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> adjustHeight </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> padding.vertical,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Size</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">paintUnit</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Canvas</span><span style="color:#24292E;"> canvas, </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> devicePixelRatio) {</span></span>
<span class="line"><span style="color:#24292E;">    ... </span><span style="color:#6A737D;">// 省略</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//文本占用的真实宽度</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> textWidth </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> painter.width;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//文本占用的真实高度</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> textHeight </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> painter.height;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 将弧度转化为度数</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> radians </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> math.pi </span><span style="color:#D73A49;">*</span><span style="color:#24292E;"> rotate </span><span style="color:#D73A49;">/</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">180</span><span style="color:#24292E;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//通过三角函数计算旋转后的位置和size</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> orgSin </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> math.</span><span style="color:#6F42C1;">sin</span><span style="color:#24292E;">(radians);</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> sin </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> orgSin.</span><span style="color:#6F42C1;">abs</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> cos </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> math.</span><span style="color:#6F42C1;">cos</span><span style="color:#24292E;">(radians).</span><span style="color:#6F42C1;">abs</span><span style="color:#24292E;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> width </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> textWidth </span><span style="color:#D73A49;">*</span><span style="color:#24292E;"> cos;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> height </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> textWidth </span><span style="color:#D73A49;">*</span><span style="color:#24292E;"> sin;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> adjustWidth </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> fontSize </span><span style="color:#D73A49;">*</span><span style="color:#24292E;"> sin;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> adjustHeight </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> fontSize </span><span style="color:#D73A49;">*</span><span style="color:#24292E;"> cos;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 为什么要平移？下面解释</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (orgSin </span><span style="color:#D73A49;">&gt;=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">) { </span><span style="color:#6A737D;">// 旋转角度为正</span></span>
<span class="line"><span style="color:#24292E;">      canvas.</span><span style="color:#6F42C1;">translate</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        adjustWidth </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> padding.left,</span></span>
<span class="line"><span style="color:#24292E;">        padding.top,</span></span>
<span class="line"><span style="color:#24292E;">      );</span></span>
<span class="line"><span style="color:#24292E;">    } </span><span style="color:#D73A49;">else</span><span style="color:#24292E;"> { </span><span style="color:#6A737D;">// 旋转角度为负</span></span>
<span class="line"><span style="color:#24292E;">      canvas.</span><span style="color:#6F42C1;">translate</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        padding.left,</span></span>
<span class="line"><span style="color:#24292E;">        height </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> padding.top,</span></span>
<span class="line"><span style="color:#24292E;">      );</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"><span style="color:#24292E;">    canvas.</span><span style="color:#6F42C1;">rotate</span><span style="color:#24292E;">(radians);</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 绘制文本</span></span>
<span class="line"><span style="color:#24292E;">    painter.</span><span style="color:#6F42C1;">paint</span><span style="color:#24292E;">(canvas, </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;">.zero);</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 返回水印单元所占的真实空间大小（需要加上padding）</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Size</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      width </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> adjustWidth </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> padding.horizontal,</span></span>
<span class="line"><span style="color:#24292E;">      height </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> adjustHeight </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> padding.vertical,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span></code></pre></div><p>注意，在旋转前我们对 canvas 进行了平移操作，如果不限平移，就会导致旋转之后一部分内容的位置跑在画布之外了，如图10-11：</p><p><img src="/assets/10-11.60e635b4.png" alt="图10-11"></p><p>接下来实现 shouldRepaint 方法：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#79B8FF;">bool</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">shouldRepaint</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">TextWaterMarkPainter</span><span style="color:#E1E4E8;"> oldPainter) {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> oldPainter.rotate </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> rotate </span><span style="color:#F97583;">||</span></span>
<span class="line"><span style="color:#E1E4E8;">      oldPainter.text </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> text </span><span style="color:#F97583;">||</span></span>
<span class="line"><span style="color:#E1E4E8;">      oldPainter.padding </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> padding </span><span style="color:#F97583;">||</span></span>
<span class="line"><span style="color:#E1E4E8;">      oldPainter.textDirection </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> textDirection </span><span style="color:#F97583;">||</span></span>
<span class="line"><span style="color:#E1E4E8;">      oldPainter.textStyle </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> textStyle;</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#005CC5;">bool</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">shouldRepaint</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">TextWaterMarkPainter</span><span style="color:#24292E;"> oldPainter) {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> oldPainter.rotate </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> rotate </span><span style="color:#D73A49;">||</span></span>
<span class="line"><span style="color:#24292E;">      oldPainter.text </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> text </span><span style="color:#D73A49;">||</span></span>
<span class="line"><span style="color:#24292E;">      oldPainter.padding </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> padding </span><span style="color:#D73A49;">||</span></span>
<span class="line"><span style="color:#24292E;">      oldPainter.textDirection </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> textDirection </span><span style="color:#D73A49;">||</span></span>
<span class="line"><span style="color:#24292E;">      oldPainter.textStyle </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> textStyle;</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>上面这些属性发生变化时都会导致水印 UI 发生变化，所以需要重绘。</p><h3 id="测试" tabindex="-1">测试 <a class="header-anchor" href="#测试" aria-label="Permalink to &quot;测试&quot;">​</a></h3><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">wTextWaterMark</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">wTextWaterMark</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Stack</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    children</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> [</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#B392F0;">wPage</span><span style="color:#E1E4E8;">(),</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">IgnorePointer</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          painter</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextWaterMarkPainter</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            text</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#9ECBFF;">&#39;Flutter 中国 @wendux&#39;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">            textStyle</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextStyle</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">              fontSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">15</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">              fontWeight</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">FontWeight</span><span style="color:#E1E4E8;">.w200,</span></span>
<span class="line"><span style="color:#E1E4E8;">              color</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Colors</span><span style="color:#E1E4E8;">.black38, </span><span style="color:#6A737D;">//为了水印能更清晰一些，颜色深一点</span></span>
<span class="line"><span style="color:#E1E4E8;">            ),</span></span>
<span class="line"><span style="color:#E1E4E8;">            rotate</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">20</span><span style="color:#E1E4E8;">, </span><span style="color:#6A737D;">// 旋转 -20 度</span></span>
<span class="line"><span style="color:#E1E4E8;">          ),</span></span>
<span class="line"><span style="color:#E1E4E8;">        ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">    ],</span></span>
<span class="line"><span style="color:#E1E4E8;">  );</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">wPage</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Center</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ElevatedButton</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Text</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;按钮&#39;</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">      onPressed</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> () </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">print</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;tab&#39;</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">    ),</span></span>
<span class="line"><span style="color:#E1E4E8;">  );</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span>
<span class="line"><span style="color:#E1E4E8;">... </span><span style="color:#6A737D;">//省略无关代码</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">wTextWaterMark</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">wTextWaterMark</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Stack</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    children</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> [</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6F42C1;">wPage</span><span style="color:#24292E;">(),</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">IgnorePointer</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          painter</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextWaterMarkPainter</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            text</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#032F62;">&#39;Flutter 中国 @wendux&#39;</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">            textStyle</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextStyle</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">              fontSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">15</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">              fontWeight</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">FontWeight</span><span style="color:#24292E;">.w200,</span></span>
<span class="line"><span style="color:#24292E;">              color</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Colors</span><span style="color:#24292E;">.black38, </span><span style="color:#6A737D;">//为了水印能更清晰一些，颜色深一点</span></span>
<span class="line"><span style="color:#24292E;">            ),</span></span>
<span class="line"><span style="color:#24292E;">            rotate</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">20</span><span style="color:#24292E;">, </span><span style="color:#6A737D;">// 旋转 -20 度</span></span>
<span class="line"><span style="color:#24292E;">          ),</span></span>
<span class="line"><span style="color:#24292E;">        ),</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">    ],</span></span>
<span class="line"><span style="color:#24292E;">  );</span></span>
<span class="line"><span style="color:#24292E;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">wPage</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Center</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ElevatedButton</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Text</span><span style="color:#24292E;">(</span><span style="color:#032F62;">&#39;按钮&#39;</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">      onPressed</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> () </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">print</span><span style="color:#24292E;">(</span><span style="color:#032F62;">&#39;tab&#39;</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">    ),</span></span>
<span class="line"><span style="color:#24292E;">  );</span></span>
<span class="line"><span style="color:#24292E;">}</span></span>
<span class="line"><span style="color:#24292E;">... </span><span style="color:#6A737D;">//省略无关代码</span></span></code></pre></div><p>运行后效果如图10-12：</p><p><img src="/assets/10-12.1d08bea7.png" alt="图10-12"></p><h2 id="_10-8-4-单元水印画笔—交错文本水印" tabindex="-1">10.8.4 单元水印画笔—交错文本水印 <a class="header-anchor" href="#_10-8-4-单元水印画笔—交错文本水印" aria-label="Permalink to &quot;10.8.4 单元水印画笔—交错文本水印&quot;">​</a></h2><p>拥有交错效果的文本水印比较常见，效果如图10-13：</p><p><img src="/assets/10-13.ba5ddc64.png" alt="图10-13"></p><p>要实现这样的效果按照之前思路，我们只需要将单元水印绘制为图中红色框圈出来的部分即可，可以看到这个单元水印和之前TextWaterMarkPainter 有一点不同，即 TextWaterMarkPainter 只能绘制单个文本，而现在我们需要绘制两个问文本，且两个文本沿竖直方向排列，且两个文本左边起始位置有偏移。</p><p>我们想想如何实现？直接能想到的是继续在 TextWaterMarkPainter 的 paintUnit 方法后面加逻辑，但这样会带来两个问题：</p><ol><li>TextWaterMarkPainter 的配置参数会变多。</li><li>TextWaterMarkPainter 的 paintUnit 已经很复杂了，如果再往里面加代码，后期的理解成本和维护成本会比较大，心智负担重。</li></ol><p>不能直接修改 TextWaterMarkPainter 实现，但我们有想复用 TextWaterMarkPainter 的逻辑，这时可以使用代理模式，即我们新建一个WaterMarkPainter，在里面来调用 TextWaterMarkPainter 方法即可。</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#6A737D;">/// 交错文本水印画笔，可以在水平或垂直方向上组合两个文本水印，</span></span>
<span class="line"><span style="color:#6A737D;">/// 通过给第二个文本水印指定不同的 padding 来实现交错效果。</span></span>
<span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">StaggerTextWaterMarkPainter</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMarkPainter</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">StaggerTextWaterMarkPainter</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.text,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.padding1,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.padding2 </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">EdgeInsets</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">all</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">30</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.rotate,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.textStyle,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.staggerAxis </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Axis</span><span style="color:#E1E4E8;">.vertical, </span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">String</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> text2,</span></span>
<span class="line"><span style="color:#E1E4E8;">  }) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> text2 </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> text2 </span><span style="color:#F97583;">??</span><span style="color:#E1E4E8;"> text;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//第一个文本</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">String</span><span style="color:#E1E4E8;"> text;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//第二个文本，如果不指定则和第二个文本相同</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">String</span><span style="color:#E1E4E8;"> text2;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//我们限制两个文本的旋转角度和文本样式必须相同，否则显得太乱了</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> rotate;</span></span>
<span class="line"><span style="color:#E1E4E8;">  ui.</span><span style="color:#79B8FF;">TextStyle</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> textStyle;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//第一个文本的padding</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">EdgeInsets</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> padding1;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//第二个文本的padding</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">EdgeInsets</span><span style="color:#E1E4E8;"> padding2;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 两个文本沿哪个方向排列</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Axis</span><span style="color:#E1E4E8;"> staggerAxis;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">paintUnit</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Canvas</span><span style="color:#E1E4E8;"> canvas, </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> devicePixelRatio) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextWaterMarkPainter</span><span style="color:#E1E4E8;"> painter </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextWaterMarkPainter</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      text</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> text,</span></span>
<span class="line"><span style="color:#E1E4E8;">      padding</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> padding1,</span></span>
<span class="line"><span style="color:#E1E4E8;">      rotate</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> rotate </span><span style="color:#F97583;">??</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">      textStyle</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> textStyle,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 绘制第一个文本水印前保存画布状态，因为在绘制过程中可能会平移或旋转画布</span></span>
<span class="line"><span style="color:#E1E4E8;">    canvas.</span><span style="color:#B392F0;">save</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 绘制第一个文本水印</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> size1 </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> painter.</span><span style="color:#B392F0;">paintUnit</span><span style="color:#E1E4E8;">(canvas, devicePixelRatio);</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 绘制完毕后恢复画布状态。</span></span>
<span class="line"><span style="color:#E1E4E8;">    canvas.</span><span style="color:#B392F0;">restore</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 确定交错方向</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">bool</span><span style="color:#E1E4E8;"> vertical </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> staggerAxis </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Axis</span><span style="color:#E1E4E8;">.vertical;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 将 Canvas平移至第二个文本水印的起始绘制点</span></span>
<span class="line"><span style="color:#E1E4E8;">    canvas.</span><span style="color:#B392F0;">translate</span><span style="color:#E1E4E8;">(vertical </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> size1.width, vertical </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> size1.height </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">);</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 设置第二个文本水印的 padding 和 text2</span></span>
<span class="line"><span style="color:#E1E4E8;">    painter</span></span>
<span class="line"><span style="color:#E1E4E8;">      ..padding </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> padding2</span></span>
<span class="line"><span style="color:#E1E4E8;">      ..text </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> text2;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 绘制第二个文本水印</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> size2 </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> painter.</span><span style="color:#B392F0;">paintUnit</span><span style="color:#E1E4E8;">(canvas, devicePixelRatio);</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 返回两个文本水印所占用的总大小</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      vertical </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> math.</span><span style="color:#B392F0;">max</span><span style="color:#E1E4E8;">(size1.width, size2.width) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> size1.width </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> size2.width,</span></span>
<span class="line"><span style="color:#E1E4E8;">      vertical</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> size1.height </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> size2.height</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> math.</span><span style="color:#B392F0;">max</span><span style="color:#E1E4E8;">(size1.height, size2.height),</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">bool</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">shouldRepaint</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">StaggerTextWaterMarkPainter</span><span style="color:#E1E4E8;"> oldPainter) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> oldPainter.rotate </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> rotate </span><span style="color:#F97583;">||</span></span>
<span class="line"><span style="color:#E1E4E8;">        oldPainter.text </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> text </span><span style="color:#F97583;">||</span></span>
<span class="line"><span style="color:#E1E4E8;">        oldPainter.text2 </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> text2 </span><span style="color:#F97583;">||</span></span>
<span class="line"><span style="color:#E1E4E8;">        oldPainter.staggerAxis </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> staggerAxis </span><span style="color:#F97583;">||</span></span>
<span class="line"><span style="color:#E1E4E8;">        oldPainter.padding1 </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> padding1 </span><span style="color:#F97583;">||</span></span>
<span class="line"><span style="color:#E1E4E8;">        oldPainter.padding2 </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> padding2 </span><span style="color:#F97583;">||</span></span>
<span class="line"><span style="color:#E1E4E8;">        oldPainter.textDirection </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> textDirection </span><span style="color:#F97583;">||</span><span style="color:#E1E4E8;">      </span></span>
<span class="line"><span style="color:#E1E4E8;">        oldPainter.textStyle </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> textStyle;</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#6A737D;">/// 交错文本水印画笔，可以在水平或垂直方向上组合两个文本水印，</span></span>
<span class="line"><span style="color:#6A737D;">/// 通过给第二个文本水印指定不同的 padding 来实现交错效果。</span></span>
<span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">StaggerTextWaterMarkPainter</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMarkPainter</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">StaggerTextWaterMarkPainter</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.text,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.padding1,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.padding2 </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">EdgeInsets</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">all</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">30</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.rotate,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.textStyle,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.staggerAxis </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Axis</span><span style="color:#24292E;">.vertical, </span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">String</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> text2,</span></span>
<span class="line"><span style="color:#24292E;">  }) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> text2 </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> text2 </span><span style="color:#D73A49;">??</span><span style="color:#24292E;"> text;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//第一个文本</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">String</span><span style="color:#24292E;"> text;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//第二个文本，如果不指定则和第二个文本相同</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">String</span><span style="color:#24292E;"> text2;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//我们限制两个文本的旋转角度和文本样式必须相同，否则显得太乱了</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> rotate;</span></span>
<span class="line"><span style="color:#24292E;">  ui.</span><span style="color:#005CC5;">TextStyle</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> textStyle;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//第一个文本的padding</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">EdgeInsets</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> padding1;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//第二个文本的padding</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">EdgeInsets</span><span style="color:#24292E;"> padding2;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 两个文本沿哪个方向排列</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Axis</span><span style="color:#24292E;"> staggerAxis;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Size</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">paintUnit</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Canvas</span><span style="color:#24292E;"> canvas, </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> devicePixelRatio) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextWaterMarkPainter</span><span style="color:#24292E;"> painter </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextWaterMarkPainter</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      text</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> text,</span></span>
<span class="line"><span style="color:#24292E;">      padding</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> padding1,</span></span>
<span class="line"><span style="color:#24292E;">      rotate</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> rotate </span><span style="color:#D73A49;">??</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">      textStyle</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> textStyle,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 绘制第一个文本水印前保存画布状态，因为在绘制过程中可能会平移或旋转画布</span></span>
<span class="line"><span style="color:#24292E;">    canvas.</span><span style="color:#6F42C1;">save</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 绘制第一个文本水印</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> size1 </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> painter.</span><span style="color:#6F42C1;">paintUnit</span><span style="color:#24292E;">(canvas, devicePixelRatio);</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 绘制完毕后恢复画布状态。</span></span>
<span class="line"><span style="color:#24292E;">    canvas.</span><span style="color:#6F42C1;">restore</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 确定交错方向</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">bool</span><span style="color:#24292E;"> vertical </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> staggerAxis </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Axis</span><span style="color:#24292E;">.vertical;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 将 Canvas平移至第二个文本水印的起始绘制点</span></span>
<span class="line"><span style="color:#24292E;">    canvas.</span><span style="color:#6F42C1;">translate</span><span style="color:#24292E;">(vertical </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> size1.width, vertical </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> size1.height </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">);</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 设置第二个文本水印的 padding 和 text2</span></span>
<span class="line"><span style="color:#24292E;">    painter</span></span>
<span class="line"><span style="color:#24292E;">      ..padding </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> padding2</span></span>
<span class="line"><span style="color:#24292E;">      ..text </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> text2;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 绘制第二个文本水印</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> size2 </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> painter.</span><span style="color:#6F42C1;">paintUnit</span><span style="color:#24292E;">(canvas, devicePixelRatio);</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 返回两个文本水印所占用的总大小</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Size</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      vertical </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> math.</span><span style="color:#6F42C1;">max</span><span style="color:#24292E;">(size1.width, size2.width) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> size1.width </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> size2.width,</span></span>
<span class="line"><span style="color:#24292E;">      vertical</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> size1.height </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> size2.height</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> math.</span><span style="color:#6F42C1;">max</span><span style="color:#24292E;">(size1.height, size2.height),</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">bool</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">shouldRepaint</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">StaggerTextWaterMarkPainter</span><span style="color:#24292E;"> oldPainter) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> oldPainter.rotate </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> rotate </span><span style="color:#D73A49;">||</span></span>
<span class="line"><span style="color:#24292E;">        oldPainter.text </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> text </span><span style="color:#D73A49;">||</span></span>
<span class="line"><span style="color:#24292E;">        oldPainter.text2 </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> text2 </span><span style="color:#D73A49;">||</span></span>
<span class="line"><span style="color:#24292E;">        oldPainter.staggerAxis </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> staggerAxis </span><span style="color:#D73A49;">||</span></span>
<span class="line"><span style="color:#24292E;">        oldPainter.padding1 </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> padding1 </span><span style="color:#D73A49;">||</span></span>
<span class="line"><span style="color:#24292E;">        oldPainter.padding2 </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> padding2 </span><span style="color:#D73A49;">||</span></span>
<span class="line"><span style="color:#24292E;">        oldPainter.textDirection </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> textDirection </span><span style="color:#D73A49;">||</span><span style="color:#24292E;">      </span></span>
<span class="line"><span style="color:#24292E;">        oldPainter.textStyle </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> textStyle;</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>上面代码有三点需要注意：</p><ol><li>在绘制第一个文本之前需要调用 canvas.save 保存画布状态，因为在绘制过程中可能会平移或旋转画布，在绘制第二个文本之前恢复画布状态，并需要将 Canvas平移至第二个文本水印的起始绘制点。</li><li>两个文本可以沿水平方向排列，也可以沿竖直方向排列，不同的排列规则会影响最终水印单元的大小。</li><li>交错的偏移通过 padding2 来指定。</li></ol><h3 id="测试-1" tabindex="-1">测试 <a class="header-anchor" href="#测试-1" aria-label="Permalink to &quot;测试&quot;">​</a></h3><p>下面代码运行后就可以看到图10-13的效果了：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">wStaggerTextWaterMark</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Stack</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    children</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> [</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#B392F0;">wPage</span><span style="color:#E1E4E8;">(),</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">IgnorePointer</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          painter</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">StaggerTextWaterMarkPainter</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            text</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#9ECBFF;">&#39;《Flutter实战》&#39;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">            text2</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#9ECBFF;">&#39;wendux&#39;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">            textStyle</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextStyle</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">              color</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Colors</span><span style="color:#E1E4E8;">.black38,</span></span>
<span class="line"><span style="color:#E1E4E8;">            ),</span></span>
<span class="line"><span style="color:#E1E4E8;">            padding2</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">EdgeInsets</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">only</span><span style="color:#E1E4E8;">(left</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">40</span><span style="color:#E1E4E8;">), </span><span style="color:#6A737D;">// 第二个文本左边向右偏移 40</span></span>
<span class="line"><span style="color:#E1E4E8;">            rotate</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">10</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">          ),</span></span>
<span class="line"><span style="color:#E1E4E8;">        ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">    ],</span></span>
<span class="line"><span style="color:#E1E4E8;">  );</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">wStaggerTextWaterMark</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Stack</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    children</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> [</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6F42C1;">wPage</span><span style="color:#24292E;">(),</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">IgnorePointer</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          painter</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">StaggerTextWaterMarkPainter</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            text</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#032F62;">&#39;《Flutter实战》&#39;</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">            text2</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#032F62;">&#39;wendux&#39;</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">            textStyle</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextStyle</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">              color</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Colors</span><span style="color:#24292E;">.black38,</span></span>
<span class="line"><span style="color:#24292E;">            ),</span></span>
<span class="line"><span style="color:#24292E;">            padding2</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">EdgeInsets</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">only</span><span style="color:#24292E;">(left</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">40</span><span style="color:#24292E;">), </span><span style="color:#6A737D;">// 第二个文本左边向右偏移 40</span></span>
<span class="line"><span style="color:#24292E;">            rotate</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">10</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">          ),</span></span>
<span class="line"><span style="color:#24292E;">        ),</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">    ],</span></span>
<span class="line"><span style="color:#24292E;">  );</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><h2 id="_10-8-5-对水印应用偏移" tabindex="-1">10.8.5 对水印应用偏移 <a class="header-anchor" href="#_10-8-5-对水印应用偏移" aria-label="Permalink to &quot;10.8.5 对水印应用偏移&quot;">​</a></h2><p>我们实现的两个文本水印画笔能对单元水印指定padding，但是如果我们需要对整个水印组件应用偏移效果呢？比如期望如图10-14所示的效果：让 WaterMark 的整个背景向左平移了30像素，可以看到第一列的水印文本只显示了一部分。</p><p><img src="/assets/10-14.dab4f7d2.png" alt="图10-14"></p><p>首先，我们不能在文本水印画笔中应用偏移，因为水印画笔画的是单元水印，如果我们绘制的单元水印只显示了部分文本，则单元水印重复时每个重复区域也都只显示部分文本。所以我们得对 WaterMark 的背景整体做一个偏移，这时想必读者应该想到了 Transform 组件，OK，那我们先用 Transform 组件来试试。</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#79B8FF;">Transform</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">translate</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">  offset</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;">(</span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">30</span><span style="color:#E1E4E8;">,</span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">), </span><span style="color:#6A737D;">//向做偏移30像素</span></span>
<span class="line"><span style="color:#E1E4E8;">  child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    painter</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextWaterMarkPainter</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      text</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#9ECBFF;">&#39;Flutter 中国 @wendux&#39;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">      textStyle</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextStyle</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        color</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Colors</span><span style="color:#E1E4E8;">.black38,</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      rotate</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">20</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    ),</span></span>
<span class="line"><span style="color:#E1E4E8;">  ),</span></span>
<span class="line"><span style="color:#E1E4E8;">),</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#005CC5;">Transform</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">translate</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">  offset</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;">(</span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">30</span><span style="color:#24292E;">,</span><span style="color:#005CC5;">0</span><span style="color:#24292E;">), </span><span style="color:#6A737D;">//向做偏移30像素</span></span>
<span class="line"><span style="color:#24292E;">  child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    painter</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextWaterMarkPainter</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      text</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#032F62;">&#39;Flutter 中国 @wendux&#39;</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">      textStyle</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextStyle</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        color</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Colors</span><span style="color:#24292E;">.black38,</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">      rotate</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">20</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    ),</span></span>
<span class="line"><span style="color:#24292E;">  ),</span></span>
<span class="line"><span style="color:#24292E;">),</span></span></code></pre></div><p>运行后效果如图10-15：</p><p><img src="/assets/10-15.6dab249f.png" alt="图10-15"></p><p>可以发现虽然整体向做偏移了，但是右边出现了空白，这时因为 WaterMark 占用的空间本来就是和屏幕等宽的，所以它绘制时的区域也就和屏幕一样大，而Transform.translate 的作用相当于是在绘制时将绘制的原点向做平移了 30 像素，所以右边就出现了空白。</p><p>既然如此，那如果能让 WaterMark 的绘制区域超过屏幕宽度 30 像素，这样平移后不就可以了么？这个思路是对的，我们知道 WaterMark 中是通过 DecoratedBox 去绘制的背景，但我们不能去修改 DecoratedBox 的绘制逻辑，如果将 DecoratedBox 相关代码拷贝一份出来修改，这样后期的维护成本就很大，所以直接修改 DecoratedBox 的方法不可取。</p><h3 id="_1-方案一-使用可滚动组件来应用偏移" tabindex="-1">1. 方案一：使用可滚动组件来应用偏移 <a class="header-anchor" href="#_1-方案一-使用可滚动组件来应用偏移" aria-label="Permalink to &quot;1. 方案一：使用可滚动组件来应用偏移&quot;">​</a></h3><p>我们知道大多数组件的绘制区域是和自身布局大小是相同的，那么我们能不能强制让 WaterMark 的宽度超出屏幕宽度30 像素呢？当然可以，可滚动组件不都是这个原理么！那么肯定有一个方法能行的通，即强制指定WaterMark的宽度比屏幕宽度大30，然后用一个 SingleChildScrollView包裹：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">wTextWaterMarkWithOffset</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Stack</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    children</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> [</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#B392F0;">wPage</span><span style="color:#E1E4E8;">(),</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">IgnorePointer</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">LayoutBuilder</span><span style="color:#E1E4E8;">(builder</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> (context, constraints) {</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#B392F0;">print</span><span style="color:#E1E4E8;">(constraints);</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SingleChildScrollView</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            scrollDirection</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Axis</span><span style="color:#E1E4E8;">.horizontal,</span></span>
<span class="line"><span style="color:#E1E4E8;">            child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Transform</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">translate</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">              offset</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;">(</span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">30</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">              child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SizedBox</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">                </span><span style="color:#6A737D;">// constraints.maxWidth 为屏幕宽度，+30 像素</span></span>
<span class="line"><span style="color:#E1E4E8;">                width</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxWidth </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">30</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">                height</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxHeight,</span></span>
<span class="line"><span style="color:#E1E4E8;">                child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">                  painter</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextWaterMarkPainter</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">                    text</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#9ECBFF;">&#39;Flutter 中国 @wendux&#39;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">                    textStyle</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextStyle</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">                      color</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Colors</span><span style="color:#E1E4E8;">.black38,</span></span>
<span class="line"><span style="color:#E1E4E8;">                    ),</span></span>
<span class="line"><span style="color:#E1E4E8;">                    rotate</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">20</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">                  ),</span></span>
<span class="line"><span style="color:#E1E4E8;">                ),</span></span>
<span class="line"><span style="color:#E1E4E8;">              ),</span></span>
<span class="line"><span style="color:#E1E4E8;">            ),</span></span>
<span class="line"><span style="color:#E1E4E8;">          );</span></span>
<span class="line"><span style="color:#E1E4E8;">        }),</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">    ],</span></span>
<span class="line"><span style="color:#E1E4E8;">  );</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">wTextWaterMarkWithOffset</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Stack</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    children</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> [</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6F42C1;">wPage</span><span style="color:#24292E;">(),</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">IgnorePointer</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">LayoutBuilder</span><span style="color:#24292E;">(builder</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> (context, constraints) {</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#6F42C1;">print</span><span style="color:#24292E;">(constraints);</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SingleChildScrollView</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            scrollDirection</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Axis</span><span style="color:#24292E;">.horizontal,</span></span>
<span class="line"><span style="color:#24292E;">            child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Transform</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">translate</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">              offset</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;">(</span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">30</span><span style="color:#24292E;">, </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">              child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SizedBox</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">                </span><span style="color:#6A737D;">// constraints.maxWidth 为屏幕宽度，+30 像素</span></span>
<span class="line"><span style="color:#24292E;">                width</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxWidth </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">30</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">                height</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxHeight,</span></span>
<span class="line"><span style="color:#24292E;">                child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">                  painter</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextWaterMarkPainter</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">                    text</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#032F62;">&#39;Flutter 中国 @wendux&#39;</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">                    textStyle</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextStyle</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">                      color</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Colors</span><span style="color:#24292E;">.black38,</span></span>
<span class="line"><span style="color:#24292E;">                    ),</span></span>
<span class="line"><span style="color:#24292E;">                    rotate</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">20</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">                  ),</span></span>
<span class="line"><span style="color:#24292E;">                ),</span></span>
<span class="line"><span style="color:#24292E;">              ),</span></span>
<span class="line"><span style="color:#24292E;">            ),</span></span>
<span class="line"><span style="color:#24292E;">          );</span></span>
<span class="line"><span style="color:#24292E;">        }),</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">    ],</span></span>
<span class="line"><span style="color:#24292E;">  );</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>上面的代码可以实现我们期望的效果（见图10-14）。</p><p>需要说明的是因为 SingleChildScrollView 被 IgnorePointer 包裹着，所以它是接收不到事件的，所以不会受用户滑动的干扰。</p><p>我们知道 SingleChildScrollView 内部要创建Scrollable 和 Viewport 对象，而在这个场景下 SingleChildScrollView 是不会响应事件的，所以创建 Scrollable 就属于多余的开销，我们需要探索一种更优的方案。</p><h3 id="_2-方案二-使用-fittedbox-来应用偏移" tabindex="-1">2. 方案二：使用 FittedBox 来应用偏移 <a class="header-anchor" href="#_2-方案二-使用-fittedbox-来应用偏移" aria-label="Permalink to &quot;2. 方案二：使用 FittedBox 来应用偏移&quot;">​</a></h3><p>我们能否先通过 UnconstrainedBox 取消父组件对子组件大小的约束，然后通过 SizedBox 指定 WaterMark 宽度比屏幕长 30 像素 来实现，比如：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#79B8FF;">LayoutBuilder</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">  builder</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> (_, constraints) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">UnconstrainedBox</span><span style="color:#E1E4E8;">( </span><span style="color:#6A737D;">// 取消父组件对子组件大小的约束</span></span>
<span class="line"><span style="color:#E1E4E8;">      alignment</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Alignment</span><span style="color:#E1E4E8;">.topRight,</span></span>
<span class="line"><span style="color:#E1E4E8;">      child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SizedBox</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#6A737D;">//指定 WaterMark 宽度比屏幕长 30 像素</span></span>
<span class="line"><span style="color:#E1E4E8;">        width</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxWidth </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">30</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">        height</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxHeight,</span></span>
<span class="line"><span style="color:#E1E4E8;">        child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;">(...),</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  },</span></span>
<span class="line"><span style="color:#E1E4E8;">),</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#005CC5;">LayoutBuilder</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">  builder</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> (_, constraints) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">UnconstrainedBox</span><span style="color:#24292E;">( </span><span style="color:#6A737D;">// 取消父组件对子组件大小的约束</span></span>
<span class="line"><span style="color:#24292E;">      alignment</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Alignment</span><span style="color:#24292E;">.topRight,</span></span>
<span class="line"><span style="color:#24292E;">      child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SizedBox</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6A737D;">//指定 WaterMark 宽度比屏幕长 30 像素</span></span>
<span class="line"><span style="color:#24292E;">        width</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxWidth </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">30</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">        height</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxHeight,</span></span>
<span class="line"><span style="color:#24292E;">        child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;">(...),</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  },</span></span>
<span class="line"><span style="color:#24292E;">),</span></span></code></pre></div><p>运行后效果如图10-16：</p><p><img src="/assets/10-16.0143222a.png" alt="图10-16"></p><p>我们看到，左边出现了一个溢出提示条，这是因为 UnconstrainedBox 虽然在其子组件布局时可以取消约束（子组件可以为无限大），但是 UnconstrainedBox 自身是受其父组件约束的，所以当 UnconstrainedBox 随着其子组件变大后，如果 UnconstrainedBox 的大小超过它父组件大小时，就导致了溢出。</p><p>如果没有这个溢出提示条，则我们想要的偏移效果实际上已经实现了！偏移的实现原理是我们指定了屏幕右对齐，因为子组件的右边界和父组件右边界对齐时，超出的 30 像素宽度就会在父组件的左边界之外，从而就实现了我们期望的效果。我们知道在 Release 模式下是不会绘制溢出提示条的，因为溢出条的绘制逻辑是在 assert 函数中，比如：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#6A737D;">// Display the overflow indicator.</span></span>
<span class="line"><span style="color:#F97583;">assert</span><span style="color:#E1E4E8;">(() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#B392F0;">paintOverflowIndicator</span><span style="color:#E1E4E8;">(context, offset, _overflowContainerRect, _overflowChildRect);</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">}());</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#6A737D;">// Display the overflow indicator.</span></span>
<span class="line"><span style="color:#D73A49;">assert</span><span style="color:#24292E;">(() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6F42C1;">paintOverflowIndicator</span><span style="color:#24292E;">(context, offset, _overflowContainerRect, _overflowChildRect);</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">}());</span></span></code></pre></div><p>所以在 Release 模式下上面代码也不会有问题，但是我们还是不应该使用这种方法，因为既然有提示，则这就代表 UnconstrainedBox 子元素溢出是不被预期的行为。</p><p>原因搞清楚后，我们解决思路就是：在取消约束的同时不要让组件大小超出父组件的空间即可。而我们之前章节介绍的 FittedBox 组件，它可以取消父组件对子组件的约束并同时可以让其子组件适配 FittedBox 父组件的大小，正好符合我们的要求，下面我们修改一下代码：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#79B8FF;">LayoutBuilder</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">  builder</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> (_, constraints) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">FittedBox</span><span style="color:#E1E4E8;">( </span><span style="color:#6A737D;">//FittedBox会取消父组件对子组件的约束</span></span>
<span class="line"><span style="color:#E1E4E8;">      alignment</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Alignment</span><span style="color:#E1E4E8;">.topRight, </span><span style="color:#6A737D;">// 通过对齐方式来实现平移效果</span></span>
<span class="line"><span style="color:#E1E4E8;">      fit</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BoxFit</span><span style="color:#E1E4E8;">.none,</span><span style="color:#6A737D;">//不进行任何适配处理</span></span>
<span class="line"><span style="color:#E1E4E8;">      child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SizedBox</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#6A737D;">//指定 WaterMark 宽度比屏幕长 30 像素</span></span>
<span class="line"><span style="color:#E1E4E8;">        width</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxWidth </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">30</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">        height</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxHeight,</span></span>
<span class="line"><span style="color:#E1E4E8;">        child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          painter</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextWaterMarkPainter</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            text</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#9ECBFF;">&#39;Flutter 中国 @wendux&#39;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">            textStyle</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextStyle</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">              color</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Colors</span><span style="color:#E1E4E8;">.black38,</span></span>
<span class="line"><span style="color:#E1E4E8;">            ),</span></span>
<span class="line"><span style="color:#E1E4E8;">            rotate</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">20</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">          ),</span></span>
<span class="line"><span style="color:#E1E4E8;">        ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  },</span></span>
<span class="line"><span style="color:#E1E4E8;">),</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#005CC5;">LayoutBuilder</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">  builder</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> (_, constraints) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">FittedBox</span><span style="color:#24292E;">( </span><span style="color:#6A737D;">//FittedBox会取消父组件对子组件的约束</span></span>
<span class="line"><span style="color:#24292E;">      alignment</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Alignment</span><span style="color:#24292E;">.topRight, </span><span style="color:#6A737D;">// 通过对齐方式来实现平移效果</span></span>
<span class="line"><span style="color:#24292E;">      fit</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BoxFit</span><span style="color:#24292E;">.none,</span><span style="color:#6A737D;">//不进行任何适配处理</span></span>
<span class="line"><span style="color:#24292E;">      child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SizedBox</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6A737D;">//指定 WaterMark 宽度比屏幕长 30 像素</span></span>
<span class="line"><span style="color:#24292E;">        width</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxWidth </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">30</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">        height</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxHeight,</span></span>
<span class="line"><span style="color:#24292E;">        child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          painter</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextWaterMarkPainter</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            text</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#032F62;">&#39;Flutter 中国 @wendux&#39;</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">            textStyle</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextStyle</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">              color</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Colors</span><span style="color:#24292E;">.black38,</span></span>
<span class="line"><span style="color:#24292E;">            ),</span></span>
<span class="line"><span style="color:#24292E;">            rotate</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">20</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">          ),</span></span>
<span class="line"><span style="color:#24292E;">        ),</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  },</span></span>
<span class="line"><span style="color:#24292E;">),</span></span></code></pre></div><p>运行后，能实现我们预期的效果（见图10-14）。</p><p>FittedBox 主要的使用场景是对子组件进行一些缩放、拉升等以适配父组件的空间，而在本例的场景中我们并没有用到这个功能（适配方式制定了 BoxFit.none ），还是有点杀鸡用牛刀的感觉，那还有其他更合适的组件来解决这个问题吗？答案是有，OverflowBox ！</p><h3 id="_3-方案三-使用-overflowbox-来应用偏移" tabindex="-1">3. 方案三：使用 OverflowBox 来应用偏移 <a class="header-anchor" href="#_3-方案三-使用-overflowbox-来应用偏移" aria-label="Permalink to &quot;3. 方案三：使用 OverflowBox 来应用偏移&quot;">​</a></h3><p>OverflowBox 和 UnconstrainedBox 相同的是可以取消父组件对子组件的约束，但不同的是 <strong>OverflowBox 自身大小不会随着子组件大小而变化</strong>，它的大小只取决于其父组件的约束（约束为 constraints.biggest），即在满足父组件约束的前提下会尽可能大。我们封装一个 TranslateWithExpandedPaintingArea 组件来包裹 WaterMark 组件：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TranslateWithExpandedPaintingArea</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">StatelessWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TranslateWithExpandedPaintingArea</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.offset,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.clipBehavior </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Clip</span><span style="color:#E1E4E8;">.none,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.child,</span></span>
<span class="line"><span style="color:#E1E4E8;">  }) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key);</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Widget</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> child;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;"> offset;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Clip</span><span style="color:#E1E4E8;"> clipBehavior;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">LayoutBuilder</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      builder</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> (context, constraints) {</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> dx </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> offset.dx.</span><span style="color:#B392F0;">abs</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> dy </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> offset.dy.</span><span style="color:#B392F0;">abs</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> widget </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">OverflowBox</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#6A737D;">//平移多少，则子组件相应轴的长度增加多少</span></span>
<span class="line"><span style="color:#E1E4E8;">          minWidth</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.minWidth </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> dx,</span></span>
<span class="line"><span style="color:#E1E4E8;">          maxWidth</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxWidth </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> dx,</span></span>
<span class="line"><span style="color:#E1E4E8;">          minHeight</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.minHeight </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> dy,</span></span>
<span class="line"><span style="color:#E1E4E8;">          maxHeight</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxHeight </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> dy,</span></span>
<span class="line"><span style="color:#E1E4E8;">          alignment</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Alignment</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            </span><span style="color:#6A737D;">// 不同方向的平移，要指定不同的对齐方式</span></span>
<span class="line"><span style="color:#E1E4E8;">            offset.dx </span><span style="color:#F97583;">&lt;=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">1</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">1</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">            offset.dy </span><span style="color:#F97583;">&lt;=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">1</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">1</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">          ),</span></span>
<span class="line"><span style="color:#E1E4E8;">          child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> child,</span></span>
<span class="line"><span style="color:#E1E4E8;">        );</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#6A737D;">//超出组件布局空间的部分要剪裁掉</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (clipBehavior </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Clip</span><span style="color:#E1E4E8;">.none) {</span></span>
<span class="line"><span style="color:#E1E4E8;">          widget </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ClipRect</span><span style="color:#E1E4E8;">(clipBehavior</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> clipBehavior, child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> widget);</span></span>
<span class="line"><span style="color:#E1E4E8;">        }</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> widget;</span></span>
<span class="line"><span style="color:#E1E4E8;">      },</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TranslateWithExpandedPaintingArea</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">StatelessWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TranslateWithExpandedPaintingArea</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.offset,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.clipBehavior </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Clip</span><span style="color:#24292E;">.none,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.child,</span></span>
<span class="line"><span style="color:#24292E;">  }) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key);</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Widget</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> child;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;"> offset;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Clip</span><span style="color:#24292E;"> clipBehavior;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">LayoutBuilder</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      builder</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> (context, constraints) {</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> dx </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> offset.dx.</span><span style="color:#6F42C1;">abs</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> dy </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> offset.dy.</span><span style="color:#6F42C1;">abs</span><span style="color:#24292E;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> widget </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">OverflowBox</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#6A737D;">//平移多少，则子组件相应轴的长度增加多少</span></span>
<span class="line"><span style="color:#24292E;">          minWidth</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.minWidth </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> dx,</span></span>
<span class="line"><span style="color:#24292E;">          maxWidth</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxWidth </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> dx,</span></span>
<span class="line"><span style="color:#24292E;">          minHeight</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.minHeight </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> dy,</span></span>
<span class="line"><span style="color:#24292E;">          maxHeight</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxHeight </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> dy,</span></span>
<span class="line"><span style="color:#24292E;">          alignment</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Alignment</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            </span><span style="color:#6A737D;">// 不同方向的平移，要指定不同的对齐方式</span></span>
<span class="line"><span style="color:#24292E;">            offset.dx </span><span style="color:#D73A49;">&lt;=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">1</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">1</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">            offset.dy </span><span style="color:#D73A49;">&lt;=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">1</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">1</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">          ),</span></span>
<span class="line"><span style="color:#24292E;">          child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> child,</span></span>
<span class="line"><span style="color:#24292E;">        );</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6A737D;">//超出组件布局空间的部分要剪裁掉</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (clipBehavior </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Clip</span><span style="color:#24292E;">.none) {</span></span>
<span class="line"><span style="color:#24292E;">          widget </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ClipRect</span><span style="color:#24292E;">(clipBehavior</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> clipBehavior, child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> widget);</span></span>
<span class="line"><span style="color:#24292E;">        }</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> widget;</span></span>
<span class="line"><span style="color:#24292E;">      },</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>上面代码有三点需要说明：</p><ol><li>会根据用户指定的偏移来动态给子组件宽高增加相应的值。</li><li>我们需要根据用户指定的偏移来动态调整 OverflowBox 的对齐方式，比如要向左平移时，OverflowBox 就必须右对齐，因为右对齐后超出父容器的部分会在左边界之外，这就是我们想要的效果，如果我们没有右对齐而是左对齐，则超出屏幕的部分本来就在右边界之外，这不符合预期。</li><li>超出边界的内容默认会显示，当然本例中水印组件大小和屏幕剩余显示空间一样大，所以超出后就不会显示，但如果我们给水印组件指定一个较小的大小，就可以看到超出之后的内容了，因此，我们定义了一个剪裁的配置参数，使用者可以根据实际情况决定是否进行剪裁。</li></ol><p>所以最终的代码就是：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">wTextWaterMarkWithOffset2</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Stack</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    children</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> [</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#B392F0;">wPage</span><span style="color:#E1E4E8;">(),</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">IgnorePointer</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TranslateWithExpandedPaintingArea</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          offset</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;">(</span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">30</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">          child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">WaterMark</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            painter</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextWaterMarkPainter</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">              text</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#9ECBFF;">&#39;Flutter 中国 @wendux&#39;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">              textStyle</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">TextStyle</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">                color</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Colors</span><span style="color:#E1E4E8;">.black38,</span></span>
<span class="line"><span style="color:#E1E4E8;">              ),</span></span>
<span class="line"><span style="color:#E1E4E8;">              rotate</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">-</span><span style="color:#79B8FF;">20</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">            ),</span></span>
<span class="line"><span style="color:#E1E4E8;">          ),</span></span>
<span class="line"><span style="color:#E1E4E8;">        ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">    ],</span></span>
<span class="line"><span style="color:#E1E4E8;">  );</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">wTextWaterMarkWithOffset2</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Stack</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    children</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> [</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6F42C1;">wPage</span><span style="color:#24292E;">(),</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">IgnorePointer</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TranslateWithExpandedPaintingArea</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          offset</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;">(</span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">30</span><span style="color:#24292E;">, </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">          child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">WaterMark</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            painter</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextWaterMarkPainter</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">              text</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#032F62;">&#39;Flutter 中国 @wendux&#39;</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">              textStyle</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">TextStyle</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">                color</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Colors</span><span style="color:#24292E;">.black38,</span></span>
<span class="line"><span style="color:#24292E;">              ),</span></span>
<span class="line"><span style="color:#24292E;">              rotate</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">-</span><span style="color:#005CC5;">20</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">            ),</span></span>
<span class="line"><span style="color:#24292E;">          ),</span></span>
<span class="line"><span style="color:#24292E;">        ),</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">    ],</span></span>
<span class="line"><span style="color:#24292E;">  );</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>运行后，能实现我们预期的效果（见图10-14）。</p><h2 id="_10-8-6-总结" tabindex="-1">10.8.6 总结 <a class="header-anchor" href="#_10-8-6-总结" aria-label="Permalink to &quot;10.8.6 总结&quot;">​</a></h2><p>本节主要内容总结：</p><ol><li>水印组件的实现思路以及如何定义单元水印画笔。</li><li>如何绘制文本以及如何进行离屏渲染。</li><li>如何对水印整体应用偏移。</li><li>笔者已经将本章封装的水印组件和水印画笔添加到了 flukit 组件库，完整代码可在 flukit 库中找到。</li></ol></div></div></main><footer class="VPDocFooter" data-v-6b87e69f data-v-ef5dee53><!--[--><!--]--><div class="edit-info" data-v-ef5dee53><div class="edit-link" data-v-ef5dee53><a class="VPLink link vp-external-link-icon no-icon edit-link-button" href="https://gitee.com/luoguangguang/note/tree/master/docs/largeFrontEnd/flutter/chapter10/watermark.md" target="_blank" rel="noreferrer" data-v-ef5dee53><!--[--><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="edit-link-icon" aria-label="edit icon" data-v-ef5dee53><path d="M18,23H4c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h7c0.6,0,1,0.4,1,1s-0.4,1-1,1H4C3.4,5,3,5.4,3,6v14c0,0.6,0.4,1,1,1h14c0.6,0,1-0.4,1-1v-7c0-0.6,0.4-1,1-1s1,0.4,1,1v7C21,21.7,19.7,23,18,23z"></path><path d="M8,17c-0.3,0-0.5-0.1-0.7-0.3C7,16.5,6.9,16.1,7,15.8l1-4c0-0.2,0.1-0.3,0.3-0.5l9.5-9.5c1.2-1.2,3.2-1.2,4.4,0c1.2,1.2,1.2,3.2,0,4.4l-9.5,9.5c-0.1,0.1-0.3,0.2-0.5,0.3l-4,1C8.2,17,8.1,17,8,17zM9.9,12.5l-0.5,2.1l2.1-0.5l9.3-9.3c0.4-0.4,0.4-1.1,0-1.6c-0.4-0.4-1.2-0.4-1.6,0l0,0L9.9,12.5z M18.5,2.5L18.5,2.5L18.5,2.5z"></path></svg> 在Gitee上参与编辑此页<!--]--></a></div><!----></div><nav class="prev-next" data-v-ef5dee53><div class="pager" data-v-ef5dee53><!----></div><div class="pager" data-v-ef5dee53><a class="pager-link next" href="./largeFrontEnd/flutter/chapter1/index.html" data-v-ef5dee53><span class="desc" data-v-ef5dee53>Next page</span><span class="title" data-v-ef5dee53>第一章 初识Flutter</span></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><footer class="VPFooter has-sidebar" data-v-1919c326 data-v-e03eb2e1><div class="container" data-v-e03eb2e1><p class="message" data-v-e03eb2e1>Released under the MIT License.</p><p class="copyright" data-v-e03eb2e1>Copyright © 2023-present LG of GZU</p></div></footer><!--[--><!--]--></div></div>
    <script>window.__VP_HASH_MAP__=JSON.parse("{\"backend_index.md\":\"0cb07c7c\",\"largefrontend_electron_index.md\":\"adab8e13\",\"largefrontend_flutter_chapter1_index.md\":\"5106cc23\",\"index.md\":\"20a66851\",\"largefrontend_flutter_chapter10_intro.md\":\"5756d1e8\",\"largefrontend_flutter_chapter10_index.md\":\"87e47f16\",\"largefrontend_flutter_chapter1_flutter_intro.md\":\"9ad5fc78\",\"largefrontend_flutter_chapter12_package_and_plugin.md\":\"c2f14cd7\",\"largefrontend_flutter_chapter12_flutter_web.md\":\"f4b6b743\",\"largefrontend_flutter_chapter10_combine.md\":\"333319d8\",\"largefrontend_flutter_chapter14_flutter_ui_system.md\":\"a1011f7b\",\"largefrontend_flutter_chapter11_index.md\":\"35dcdd7c\",\"largefrontend_flutter_chapter12_index.md\":\"890b799c\",\"largefrontend_flutter_chapter13_index.md\":\"6db9b579\",\"largefrontend_flutter_chapter14_index.md\":\"87f7da6b\",\"largefrontend_flutter_chapter1_mobile_development_intro.md\":\"40c15ac2\",\"largefrontend_flutter_chapter10_turn_box.md\":\"6a792fe1\",\"largefrontend_flutter_chapter3_index.md\":\"bc1d0de5\",\"largefrontend_flutter_chapter4_stack.md\":\"3f5c3186\",\"largefrontend_flutter_chapter1_install_flutter.md\":\"4543c368\",\"largefrontend_flutter_chapter14_paint.md\":\"8f7289b0\",\"largefrontend_flutter_chapter5_padding.md\":\"9fadce05\",\"largefrontend_flutter_chapter4_index.md\":\"f9b298f7\",\"largefrontend_flutter_chapter2_flutter_package_mgr.md\":\"4a3b7161\",\"largefrontend_flutter_chapter11_socket.md\":\"e8264e44\",\"largefrontend_flutter_chapter4_intro.md\":\"1341b5fd\",\"largefrontend_flutter_chapter14_update.md\":\"e4d2c2f0\",\"largefrontend_flutter_chapter5_index.md\":\"455baa0a\",\"largefrontend_flutter_chapter4_alignment.md\":\"a6596610\",\"largefrontend_flutter_chapter13_multi_languages_support.md\":\"c113238d\",\"largefrontend_flutter_chapter11_dio.md\":\"64f4e813\",\"largefrontend_flutter_chapter2_first_flutter_app.md\":\"b650181d\",\"largefrontend_flutter_chapter2_flutter_app_debug.md\":\"67a72433\",\"largefrontend_flutter_chapter2_state_manage.md\":\"9bb80d37\",\"largefrontend_flutter_chapter6_index.md\":\"5a321b81\",\"largefrontend_flutter_chapter5_material_scaffold.md\":\"8e6b896a\",\"largefrontend_flutter_chapter3_buttons.md\":\"ef950360\",\"largefrontend_flutter_chapter10_done_widget.md\":\"c3b44318\",\"largefrontend_flutter_chapter6_single_child_scrollview.md\":\"e3f75f3b\",\"largefrontend_flutter_chapter14_paint_flow.md\":\"d2f8102a\",\"largefrontend_flutter_chapter6_intro.md\":\"125a064e\",\"largefrontend_flutter_chapter5_transform.md\":\"22708e1e\",\"largefrontend_flutter_chapter14_layer.md\":\"4ed449fc\",\"largefrontend_flutter_chapter11_websocket.md\":\"0a022e47\",\"largefrontend_flutter_chapter5_fittedbox.md\":\"db915726\",\"largefrontend_flutter_chapter2_index.md\":\"d8e937b7\",\"largefrontend_flutter_chapter3_radio_and_checkbox.md\":\"025aba21\",\"largefrontend_flutter_chapter13_locallization_implement.md\":\"dc4b83fe\",\"largefrontend_flutter_chapter2_thread_model_and_error_report.md\":\"a29199d6\",\"largefrontend_flutter_chapter5_decoratedbox.md\":\"20f093ba\",\"largefrontend_flutter_chapter14_render_object.md\":\"55a1b551\",\"largefrontend_flutter_chapter14_element_buildcontext.md\":\"4272eae4\",\"largefrontend_flutter_chapter4_wrap_and_flow.md\":\"ac41b5bd\",\"largefrontend_flutter_chapter11_file_operation.md\":\"996a3a5d\",\"largefrontend_flutter_chapter13_faq.md\":\"ba7b9c70\",\"largefrontend_webfrontend_md_10.md\":\"51e0a312\",\"largefrontend_webfrontend_md_24.md\":\"0a4abd08\",\"largefrontend_flutter_chapter14_flutter_app_startup.md\":\"9b5942a1\",\"largefrontend_flutter_chapter10_custom_paint.md\":\"8b2a0a46\",\"largefrontend_flutter_chapter2_flutter_assets_mgr.md\":\"0e38b701\",\"largefrontend_interview_01.md\":\"2ac4b56d\",\"largefrontend_flutter_chapter8_index.md\":\"e83d3d80\",\"largefrontend_webfrontend_md_16.md\":\"a5610416\",\"largefrontend_flutter_chapter9_index.md\":\"3fb342a7\",\"largefrontend_flutter_chapter8_listener.md\":\"1b9b0316\",\"largefrontend_webfrontend_md_37.md\":\"3cacf3a2\",\"largefrontend_flutter_chapter4_layoutbuilder.md\":\"3d4bf94b\",\"largefrontend_webfrontend_md_17.md\":\"ee74edfb\",\"largefrontend_webfrontend_md_13.md\":\"b180bb5b\",\"largefrontend_flutter_chapter7_index.md\":\"0eb456b2\",\"largefrontend_webfrontend_md_31.md\":\"309453c7\",\"largefrontend_webfrontend_md_25.md\":\"95fdb4fe\",\"largefrontend_webfrontend_md_12.md\":\"3b14e491\",\"largefrontend_webfrontend_md_23.md\":\"18f08d72\",\"largefrontend_flutter_chapter11_json_model.md\":\"c84d2ccf\",\"largefrontend_webfrontend_md_01.md\":\"fbb7a35f\",\"largefrontend_flutter_chapter3_progress.md\":\"9bc148d3\",\"largefrontend_webfrontend_md_18.md\":\"ee651cc6\",\"largefrontend_flutter_sponsor.md\":\"e71431a9\",\"largefrontend_webfrontend_md_36.md\":\"f22f6d0d\",\"largefrontend_webfrontend_md_07.md\":\"71c2ad1d\",\"largefrontend_webfrontend_md_19.md\":\"e7907728\",\"largefrontend_webfrontend_md_20.md\":\"5a68bb4f\",\"largefrontend_webfrontend_md_32.md\":\"91d2f39f\",\"largefrontend_flutter_chapter7_futurebuilder_and_streambuilder.md\":\"af3535c0\",\"largefrontend_flutter_chapter9_hero.md\":\"25b46cf2\",\"largefrontend_flutter_chapter8_notification.md\":\"909f6756\",\"largefrontend_webfrontend_md_08.md\":\"0d59dce4\",\"largefrontend_flutter_chapter3_img_and_icon.md\":\"8b409b55\",\"largefrontend_webfrontend_md_04.md\":\"d6c15d55\",\"largefrontend_flutter_chapter9_animated_switcher.md\":\"9c0b3119\",\"largefrontend_webfrontend_md_22.md\":\"7d6fea68\",\"largefrontend_flutter_chapter2_flutter_widget_intro.md\":\"13414fea\",\"largefrontend_flutter_chapter13_intl.md\":\"d4776610\",\"largefrontend_overview_index.md\":\"3c87f69c\",\"largefrontend_flutter_chapter5_container.md\":\"373cc694\",\"largefrontend_wechatminiprogram_index.md\":\"7f40708d\",\"largefrontend_webfrontend_md_38.md\":\"3b6bb332\",\"linux_index.md\":\"624ecc66\",\"largefrontend_webfrontend_md_35.md\":\"f0538423\",\"myteam_index.md\":\"91a2c11f\",\"largefrontend_flutter_chapter6_pageview.md\":\"36fcbc65\",\"largefrontend_webfrontend_md_03.md\":\"2e5087f0\",\"largefrontend_webfrontend_md_21.md\":\"c181316e\",\"largefrontend_flutter_chapter3_input_and_form.md\":\"e4fba9dd\",\"largefrontend_flutter_chapter14_compositing.md\":\"6d96f932\",\"largefrontend_webfrontend_md_29.md\":\"c6fada7d\",\"largefrontend_flutter_chapter4_constraints.md\":\"9e4baace\",\"largefrontend_webfrontend_md_11.md\":\"63cd9c49\",\"largefrontend_webfrontend_md_14.md\":\"1423aeb1\",\"largefrontend_flutter_chapter6_tabview.md\":\"56048fb1\",\"largefrontend_webfrontend_md_33.md\":\"fdbe86fa\",\"largefrontend_flutter_chapter6_nestedscrollview.md\":\"2a66b86a\",\"largefrontend_webfrontend_md_34.md\":\"ad8ea220\",\"largefrontend_flutter_chapter2_flutter_router.md\":\"bb6e776c\",\"largefrontend_webfrontend_md_30.md\":\"cd2cb5aa\",\"largefrontend_flutter_chapter4_flex.md\":\"ebd43d6a\",\"largefrontend_webfrontend_md_09.md\":\"eedafcb1\",\"largefrontend_webfrontend_md_40.md\":\"4d4fefa1\",\"largefrontend_flutter_join_us.md\":\"44c81d86\",\"largefrontend_flutter_chapter9_route_transition.md\":\"f56cf837\",\"largefrontend_uniapp_index.md\":\"fa387c39\",\"largefrontend_flutter_chapter4_row_and_column.md\":\"964229f5\",\"largefrontend_flutter_chapter9_stagger_animation.md\":\"315538f5\",\"largefrontend_flutter_chapter3_text.md\":\"6720230c\",\"largefrontend_flutter_chapter10_watermark.md\":\"2e4ddf03\",\"largefrontend_flutter_chapter11_http.md\":\"efd2ee64\",\"largefrontend_webfrontend_md_06.md\":\"e20b049a\",\"largefrontend_flutter_summary.md\":\"2e31b0a8\",\"largefrontend_flutter_chapter6_animatedlist.md\":\"20d9436f\",\"largefrontend_flutter_chapter5_clip.md\":\"91f4174c\",\"largefrontend_webfrontend_md_02.md\":\"40cd2099\",\"largefrontend_flutter_chapter6_gridview.md\":\"b3be7e60\",\"largefrontend_flutter_chapter6_custom_scrollview.md\":\"01be950c\",\"largefrontend_flutter_chapter11_download_with_chunks.md\":\"9379cc89\",\"largefrontend_flutter_chapter6_scroll_controller.md\":\"ffa2d5fd\",\"largefrontend_flutter_chapter9_intro.md\":\"3fc29b5f\",\"largefrontend_flutter_chapter1_dart.md\":\"6bba4e7c\",\"largefrontend_flutter_chapter7_inherited_widget.md\":\"5a21d704\",\"largefrontend_flutter_index.md\":\"cd81fbbe\",\"largefrontend_flutter_reference.md\":\"36336375\",\"largefrontend_flutter_chapter6_keepalive.md\":\"535137be\",\"largefrontend_flutter_chapter14_layout.md\":\"0d7a8df3\",\"largefrontend_flutter_chapter6_sliver.md\":\"0c8b7553\",\"largefrontend_webfrontend_md_39.md\":\"aef49a57\",\"largefrontend_webfrontend_md_15.md\":\"bf9ff1a1\",\"largefrontend_flutter_chapter14_image_and_cache.md\":\"9622c56b\",\"largefrontend_webfrontend_md_28.md\":\"83169d10\",\"largefrontend_flutter_chapter8_eventbus.md\":\"fc0db86d\",\"largefrontend_flutter_chapter7_value_listenable_builder.md\":\"a0c76121\",\"largefrontend_flutter_preface.md\":\"604b9934\",\"largefrontend_flutter_chapter7_theme.md\":\"c21036bc\",\"largefrontend_webfrontend_md_05.md\":\"f7607009\",\"largefrontend_flutter_chapter6_listview.md\":\"472910bd\",\"largefrontend_flutter_chapter7_provider.md\":\"ab590744\",\"largefrontend_flutter_chapter8_gesture.md\":\"859bbb38\",\"largefrontend_flutter_chapter9_animated_widgets.md\":\"e8cd3c4d\",\"largefrontend_webfrontend_md_26.md\":\"4928006b\",\"largefrontend_flutter_chapter10_gradient_circular_progress_demo.md\":\"b3d48a2c\",\"largefrontend_flutter_chapter8_hittest.md\":\"e963812b\",\"largefrontend_flutter_chapter7_willpopscope.md\":\"d224ecce\",\"largefrontend_flutter_chapter9_animation_structure.md\":\"672eb3c0\",\"largefrontend_flutter_chapter8_gesture_conflict.md\":\"15bbadba\",\"largefrontend_webfrontend_md_27.md\":\"0b5b5e39\",\"largefrontend_flutter_chapter7_dailog.md\":\"5f91e51a\",\"largefrontend_flutter_chapter10_custom_checkbox.md\":\"0db7d50f\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"zh-CN\",\"dir\":\"ltr\",\"title\":\"落光的Pro博客\",\"description\":\"Vite & Vue powered static site generator.\",\"base\":\"./\",\"head\":[],\"appearance\":true,\"themeConfig\":{\"search\":{\"provider\":\"local\",\"options\":{\"locales\":{\"zh\":{\"translations\":{\"button\":{\"buttonText\":\"搜索文档\",\"buttonAriaLabel\":\"搜索文档\"},\"modal\":{\"noResultsText\":\"无法找到相关结果\",\"resetButtonTitle\":\"清除查询条件\",\"footer\":{\"selectText\":\"选择\",\"navigateText\":\"切换\"}}}}}}},\"logo\":\"/icon-radius.png\",\"nav\":[{\"text\":\"大前端\",\"items\":[{\"text\":\"Web前端\",\"link\":\"/largeFrontEnd/webFrontEnd/md/01.md\"},{\"text\":\"Uni-app\",\"link\":\"/largeFrontEnd/uniapp/\"},{\"text\":\"微信小程序\",\"link\":\"/largeFrontEnd/weChatMiniProgram/\"},{\"text\":\"Electron\",\"link\":\"/largeFrontEnd/electron/\"},{\"text\":\"Flutter\",\"link\":\"/largeFrontEnd/flutter/\"},{\"text\":\"前端面试题\",\"link\":\"/largeFrontEnd/interview/01.md\"}]},{\"text\":\"后端\",\"link\":\"/backEnd/\"},{\"text\":\"Linux\",\"link\":\"/linux/\"},{\"text\":\"我的团队\",\"link\":\"/myTeam/\"}],\"sidebar\":{\"/largeFrontEnd/webFrontEnd/\":[{\"text\":\"第一阶段  基础入门\",\"collapsed\":true,\"items\":[{\"text\":\"一、基础认知\",\"link\":\"/largeFrontEnd/webFrontEnd/md/01.md\"},{\"text\":\"二、HTML标签学习\",\"link\":\"/largeFrontEnd/webFrontEnd/md/02.md\"},{\"text\":\"三、HTML基础\",\"link\":\"/largeFrontEnd/webFrontEnd/md/03.md\"},{\"text\":\"四、CSS基础\",\"link\":\"/largeFrontEnd/webFrontEnd/md/04.md\"},{\"text\":\"五、CSS进阶\",\"link\":\"/largeFrontEnd/webFrontEnd/md/05.md\"},{\"text\":\"六、盒子模型\",\"link\":\"/largeFrontEnd/webFrontEnd/md/06.md\"},{\"text\":\"七、CSS浮动\",\"link\":\"/largeFrontEnd/webFrontEnd/md/07.md\"},{\"text\":\"八、CSS定位装饰\",\"link\":\"/largeFrontEnd/webFrontEnd/md/08.md\"},{\"text\":\"九、CSS精灵图\",\"link\":\"/largeFrontEnd/webFrontEnd/md/09.md\"},{\"text\":\"项目前置认知\",\"link\":\"/largeFrontEnd/webFrontEnd/md/10.md\"},{\"text\":\"项目结构搭建\",\"link\":\"/largeFrontEnd/webFrontEnd/md/11.md\"},{\"text\":\"字体图标、动画\",\"link\":\"/largeFrontEnd/webFrontEnd/md/12.md\"},{\"text\":\"移动web开发\",\"link\":\"/largeFrontEnd/webFrontEnd/md/13.md\"}]},{\"text\":\"第二阶段 技术进阶\",\"collapsed\":true,\"items\":[{\"text\":\"JavaScript深入浅出\",\"link\":\"/largeFrontEnd/webFrontEnd/md/14.md\"},{\"text\":\"JavaScript核心之web APIs\",\"link\":\"/largeFrontEnd/webFrontEnd/md/15.md\"},{\"text\":\"一、Web APIS导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/16.md\"},{\"text\":\"二、DOM导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/17.md\"},{\"text\":\"三、事件高级导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/18.md\"},{\"text\":\"四、BOM导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/19.md\"},{\"text\":\"五、PC网页特效导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/20.md\"},{\"text\":\"六、移动端特效导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/21.md\"},{\"text\":\"七、本地存储导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/22.md\"},{\"text\":\"jQuery入门导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/23.md\"},{\"text\":\"数据可视化项目导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/24.md\"}]},{\"text\":\"Node.js\",\"collapsed\":true,\"items\":[{\"text\":\"buffer(缓冲器)\",\"link\":\"/largeFrontEnd/webFrontEnd/md/25.md\"},{\"text\":\"计算机基础\",\"link\":\"/largeFrontEnd/webFrontEnd/md/26.md\"},{\"text\":\"fs模块\",\"link\":\"/largeFrontEnd/webFrontEnd/md/27.md\"},{\"text\":\"path模块\",\"link\":\"/largeFrontEnd/webFrontEnd/md/28.md\"},{\"text\":\"HTTP模块\",\"link\":\"/largeFrontEnd/webFrontEnd/md/29.md\"},{\"text\":\"Express框架\",\"link\":\"/largeFrontEnd/webFrontEnd/md/30.md\"},{\"text\":\"Mongodb\",\"link\":\"/largeFrontEnd/webFrontEnd/md/31.md\"}]},{\"text\":\"Ajax\",\"link\":\"/largeFrontEnd/webFrontEnd/md/32.md\"},{\"text\":\"前端面试题\",\"collapsed\":true,\"items\":[{\"text\":\"一、CSS\",\"link\":\"/largeFrontEnd/webFrontEnd/md/33.md\"},{\"text\":\"二、JavaScript\",\"link\":\"/largeFrontEnd/webFrontEnd/md/34.md\"},{\"text\":\"三、HTML5CSS3\",\"link\":\"/largeFrontEnd/webFrontEnd/md/35.md\"},{\"text\":\"四、Vue\",\"link\":\"/largeFrontEnd/webFrontEnd/md/36.md\"},{\"text\":\"五、Echarts\",\"link\":\"/largeFrontEnd/webFrontEnd/md/37.md\"},{\"text\":\"六.Uni-APP\",\"link\":\"/largeFrontEnd/webFrontEnd/md/38.md\"},{\"text\":\"七、webpack\",\"link\":\"/largeFrontEnd/webFrontEnd/md/39.md\"},{\"text\":\"八、Git\",\"link\":\"/largeFrontEnd/webFrontEnd/md/40.md\"}]}],\"/largeFrontEnd/flutter/\":[{\"text\":\"Flutter入门到实战\",\"collapsed\":false,\"items\":[{\"text\":\"第一章 初识Flutter\",\"link\":\"/largeFrontEnd/flutter/chapter1/index.md\"},{\"text\":\"第二章 简介\",\"link\":\"/largeFrontEnd/flutter/chapter2/index.md\"},{\"text\":\"第三章 基础组件\",\"link\":\"/largeFrontEnd/flutter/chapter3/index.md\"},{\"text\":\"第四章 布局类组件\",\"link\":\"/largeFrontEnd/flutter/chapter4/index.md\"},{\"text\":\"第五章 容器类Widget\",\"link\":\"/largeFrontEnd/flutter/chapter5/index.md\"},{\"text\":\"第六章 可滚动组件\",\"link\":\"/largeFrontEnd/flutter/chapter6/index.md\"},{\"text\":\"第七章 功能型Widget简介\",\"link\":\"/largeFrontEnd/flutter/chapter7/index.md\"},{\"text\":\"第八章 事件处理与通知\",\"link\":\"/largeFrontEnd/flutter/chapter8/index.md\"},{\"text\":\"第九章 动画\",\"link\":\"/largeFrontEnd/flutter/chapter9/index.md\"},{\"text\":\"第十章 自定义组件\",\"link\":\"/largeFrontEnd/flutter/chapter10/index.md\"},{\"text\":\"第十一章 文件操作\",\"link\":\"/largeFrontEnd/flutter/chapter11/index.md\"},{\"text\":\"第十二章 Flutter 扩展\",\"link\":\"/largeFrontEnd/flutter/chapter12/index.md\"},{\"text\":\"第十三章 多语言\",\"link\":\"/largeFrontEnd/flutter/chapter13/index.md\"},{\"text\":\"第十四章 高级进阶\",\"link\":\"/largeFrontEnd/flutter/chapter14/index.md\"}]}]},\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://gitee.com/luoguangguang\"},{\"icon\":{\"svg\":\"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Dribbble</title><path d=\\\"M12...6.38z\\\"/></svg>\"},\"link\":\"...\",\"ariaLabel\":\"cool link\"}],\"footer\":{\"message\":\"Released under the MIT License.\",\"copyright\":\"Copyright © 2023-present LG of GZU\"},\"editLink\":{\"pattern\":\"https://gitee.com/luoguangguang/note/tree/master/docs/:path\",\"text\":\"在Gitee上参与编辑此页\"},\"carbonAds\":{\"code\":\"your-carbon-code\",\"placement\":\"your-carbon-placement\"}},\"locales\":{},\"scrollOffset\":90,\"cleanUrls\":false}");</script>
    
  </body>
</html>